Compare commits
28
Commits
3c429b6c51
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fcdf902f7 | ||
|
|
e3a1195c04 | ||
|
|
db69452213 | ||
|
|
a350a591ad | ||
|
|
6a354bd7a7 | ||
|
|
23d7045ec1 | ||
|
|
3492f93c83 | ||
|
|
d1de56a21d | ||
|
|
880f90211b | ||
|
|
1a44240f88 | ||
|
|
071347f4d4 | ||
|
|
821b865f2d | ||
|
|
50f01429b0 | ||
|
|
930a84a0f6 | ||
|
|
447d94fdd6 | ||
|
|
f0bd2c0b73 | ||
|
|
119a4d0341 | ||
|
|
53b40a4d2f | ||
|
|
0533cdab23 | ||
|
|
8c61aa1e1d | ||
|
|
77924b5937 | ||
|
|
2c811503a2 | ||
|
|
18a55272a3 | ||
|
|
1658127bdd | ||
|
|
72a8c13c1f | ||
|
|
aececd8e5d | ||
|
|
883cba901c | ||
|
|
a1874f3682 |
+198
-74
@@ -34,7 +34,7 @@ clsid = "{B5291320-FE7C-4069-BF87-A0AC327FCD20}"
|
|||||||
#
|
#
|
||||||
# This key is optional. If you don't fill this key,
|
# This key is optional. If you don't fill this key,
|
||||||
# Windows will use your executable embedded Win32 resources for friendly name.
|
# Windows will use your executable embedded Win32 resources for friendly name.
|
||||||
name = "default"
|
name = "self"
|
||||||
|
|
||||||
# The icon of this program.
|
# The icon of this program.
|
||||||
#
|
#
|
||||||
@@ -42,7 +42,7 @@ name = "default"
|
|||||||
#
|
#
|
||||||
# This key is optional. If you don't fill this key,
|
# This key is optional. If you don't fill this key,
|
||||||
# Windows will use the first icon embedded in your executable as icon.
|
# Windows will use the first icon embedded in your executable as icon.
|
||||||
icon = "others"
|
icon = "self"
|
||||||
|
|
||||||
# The default behavior of your program.
|
# The default behavior of your program.
|
||||||
#
|
#
|
||||||
@@ -69,31 +69,41 @@ behavior = "generic"
|
|||||||
#
|
#
|
||||||
# Due to the same use of back-slash in path part,
|
# Due to the same use of back-slash in path part,
|
||||||
# you can utilize TOML "literal strings" syntax to write this value easier.
|
# you can utilize TOML "literal strings" syntax to write this value easier.
|
||||||
default = '@C:\path\to\ppic.exe,-1001'
|
# self = '@C:\path\to\ppic.exe,-1001'
|
||||||
|
|
||||||
# The value of this key also can be a plain string like this commente statement,
|
# The value of this key also can be a plain string like this commente statement,
|
||||||
# if you don't want to touch Win32 resources and your program do not have any I18N requirements.
|
# if you don't want to touch Win32 resources and your program do not have any I18N requirements.
|
||||||
# default = "Pineapple Picture"
|
self = "Pineapple Pictures"
|
||||||
|
|
||||||
# And more string resources...
|
# And more string resources...
|
||||||
jpg = '@C:\path\to\ppic.exe,-1011'
|
jpeg = 'JPEG Image'
|
||||||
jfif = '@C:\path\to\ppic.exe,-1050'
|
gif = 'GIF Animation Image'
|
||||||
gif = '@C:\path\to\ppic.exe,-1012'
|
png = 'PNG Image'
|
||||||
bmp = '@C:\path\to\ppic.exe,-1013'
|
svg = 'Scalable Vector Graphics'
|
||||||
png = '@C:\path\to\ppic.exe,-1014'
|
webp = 'WebP Image'
|
||||||
ico = '@C:\path\to\ppic.exe,-1015'
|
avif = 'AV1 Image File'
|
||||||
jpeg = '@C:\path\to\ppic.exe,-1016'
|
ico = 'Windows Icon Image'
|
||||||
tif = '@C:\path\to\ppic.exe,-1017'
|
icns = 'Apple Icon Image'
|
||||||
tiff = '@C:\path\to\ppic.exe,-1018'
|
psd = 'Photoshop Document'
|
||||||
webp = '@C:\path\to\ppic.exe,-1019'
|
kra = 'Krita Document'
|
||||||
avif = '@C:\path\to\ppic.exe,-1020'
|
xcf = 'GIMP Document'
|
||||||
svg = '@C:\path\to\ppic.exe,-1021'
|
tga = 'TARGA Image'
|
||||||
kra = '@C:\path\to\ppic.exe,-1022'
|
tif = 'Tagged Image File Format'
|
||||||
xcf = '@C:\path\to\ppic.exe,-1023'
|
bmp = 'Bitmap'
|
||||||
qoi = '@C:\path\to\ppic.exe,-1024'
|
wbmp = 'Wireless Bitmap'
|
||||||
apng = '@C:\path\to\ppic.exe,-1025'
|
dds = 'DirectDraw Surface'
|
||||||
exr = '@C:\path\to\ppic.exe,-1026'
|
hdr = 'High Dynamic Range Image'
|
||||||
tga = '@C:\path\to\ppic.exe,-1027'
|
iff = 'Interchange File Format'
|
||||||
|
pic = 'Softimage Picture'
|
||||||
|
pcx = 'PiCture eXchange PC Paintbrush Image'
|
||||||
|
qoi = 'Quite OK Image'
|
||||||
|
ras = 'Sun Raster Image'
|
||||||
|
ora = 'OpenRaster Image'
|
||||||
|
ani = 'Animated Cursor'
|
||||||
|
pfm = 'Portable Float Map'
|
||||||
|
rgb = 'Silicon Graphics Image'
|
||||||
|
pxr = 'Pixar Raster Image'
|
||||||
|
sct = 'Scitex Continuous Tone'
|
||||||
|
|
||||||
# ======== Icon Resources ========
|
# ======== Icon Resources ========
|
||||||
|
|
||||||
@@ -113,18 +123,17 @@ tga = '@C:\path\to\ppic.exe,-1027'
|
|||||||
#
|
#
|
||||||
# Due to the same use of back-slash in path part,
|
# Due to the same use of back-slash in path part,
|
||||||
# you can utilize TOML "literal strings" syntax to write this value easier.
|
# you can utilize TOML "literal strings" syntax to write this value easier.
|
||||||
png = 'C:\path\to\ppic.exe,-1'
|
self = 'C:\path\to\ppic.exe,0'
|
||||||
|
|
||||||
# The value of this key also can be the absolute path to existing icon file like this commente statement.
|
# The value of this key also can be the absolute path to existing icon file like this commente statement.
|
||||||
# png = 'C:\path\to\ppic\icons\png.ico'
|
generic = 'C:\path\to\ppic\icons\generic.ico'
|
||||||
|
|
||||||
# Add more icon resources...
|
# Add more icon resources...
|
||||||
svg = 'C:\path\to\ppic.exe,-2'
|
gif = 'C:\path\to\ppic\icons\gif.ico'
|
||||||
gif = 'C:\path\to\ppic.exe,-3'
|
jpeg = 'C:\path\to\ppic\icons\jpeg.ico'
|
||||||
jpg = 'C:\path\to\ppic.exe,-4'
|
png = 'C:\path\to\ppic\icons\png.ico'
|
||||||
webp = 'C:\path\to\ppic.exe,-5'
|
psd = 'C:\path\to\ppic\icons\psd.ico'
|
||||||
avif = 'C:\path\to\ppic.exe,-6'
|
svg = 'C:\path\to\ppic\icons\svg.ico'
|
||||||
others = 'C:\path\to\ppic.exe,-61'
|
|
||||||
|
|
||||||
# ======== Behaviors Collection ========
|
# ======== Behaviors Collection ========
|
||||||
|
|
||||||
@@ -162,76 +171,191 @@ generic = '"C:\path\to\ppic.exe" "%1"'
|
|||||||
[exts.jpg]
|
[exts.jpg]
|
||||||
# First, we specify its name shown in Windows Explorer.
|
# First, we specify its name shown in Windows Explorer.
|
||||||
# The value of this field is the token declared above in "strs" list.
|
# The value of this field is the token declared above in "strs" list.
|
||||||
name = "jpg"
|
name = "jpeg"
|
||||||
# Then, speficy its associated icon.
|
# Then, speficy its associated icon.
|
||||||
# The value of this field is the token declared above in "icons" list.
|
# The value of this field is the token declared above in "icons" list.
|
||||||
icon = "jpg"
|
icon = "jpeg"
|
||||||
# At last, specify the behavior of this extension define decide how we open this file.
|
# At last, specify the behavior of this extension define decide how we open this file.
|
||||||
# The value of this field is the token declared above in "behaviors" list.
|
# The value of this field is the token declared above in "behaviors" list.
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
|
|
||||||
# And more file extensions...
|
# And more file extensions...
|
||||||
[exts.jfif]
|
[exts.jpeg]
|
||||||
name = "jfif"
|
name = "jpeg"
|
||||||
icon = "jpg"
|
icon = "jpeg"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
|
[exts.jfif]
|
||||||
|
name = "jpeg"
|
||||||
|
icon = "jpeg"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
[exts.gif]
|
[exts.gif]
|
||||||
name = "gif"
|
name = "gif"
|
||||||
icon = "gif"
|
icon = "gif"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
[exts.bmp]
|
|
||||||
name = "bmp"
|
|
||||||
icon = "others"
|
|
||||||
behavior = "generic"
|
|
||||||
[exts.png]
|
[exts.png]
|
||||||
name = "png"
|
name = "png"
|
||||||
icon = "png"
|
icon = "png"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
[exts.ico]
|
|
||||||
name = "ico"
|
|
||||||
icon = "others"
|
|
||||||
behavior = "generic"
|
|
||||||
[exts.jpeg]
|
|
||||||
name = "jpeg"
|
|
||||||
icon = "jpg"
|
|
||||||
behavior = "generic"
|
|
||||||
[exts.tif]
|
|
||||||
name = "tif"
|
|
||||||
icon = "others"
|
|
||||||
behavior = "generic"
|
|
||||||
[exts.tiff]
|
|
||||||
name = "tiff"
|
|
||||||
icon = "others"
|
|
||||||
behavior = "generic"
|
|
||||||
[exts.webp]
|
|
||||||
name = "webp"
|
|
||||||
icon = "webp"
|
|
||||||
behavior = "generic"
|
|
||||||
[exts.svg]
|
[exts.svg]
|
||||||
name = "svg"
|
name = "svg"
|
||||||
icon = "svg"
|
icon = "svg"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
[exts.kra]
|
|
||||||
name = "kra"
|
[exts.webp]
|
||||||
icon = "others"
|
name = "webp"
|
||||||
behavior = "generic"
|
icon = "generic"
|
||||||
[exts.xcf]
|
|
||||||
name = "xcf"
|
|
||||||
icon = "others"
|
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
|
|
||||||
[exts.avif]
|
[exts.avif]
|
||||||
name = "avif"
|
name = "avif"
|
||||||
icon = "avif"
|
icon = "generic"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.ico]
|
||||||
|
name = "ico"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.icns]
|
||||||
|
name = "icns"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.psd]
|
||||||
|
name = "psd"
|
||||||
|
icon = "psd"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.psb]
|
||||||
|
name = "psd"
|
||||||
|
icon = "psd"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.pdd]
|
||||||
|
name = "psd"
|
||||||
|
icon = "psd"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.psdt]
|
||||||
|
name = "psd"
|
||||||
|
icon = "psd"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.kra]
|
||||||
|
name = "kra"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.xcf]
|
||||||
|
name = "xcf"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.tga]
|
||||||
|
name = "tga"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.tif]
|
||||||
|
name = "tif"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.tiff]
|
||||||
|
name = "tif"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.bmp]
|
||||||
|
name = "bmp"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.wbmp]
|
||||||
|
name = "wbmp"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.dds]
|
||||||
|
name = "dds"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.hdr]
|
||||||
|
name = "hdr"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.iff]
|
||||||
|
name = "iff"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.pic]
|
||||||
|
name = "pic"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.pcx]
|
||||||
|
name = "pcx"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
[exts.qoi]
|
[exts.qoi]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
icon = "others"
|
icon = "generic"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
[exts.apng]
|
|
||||||
name = "apng"
|
[exts.ras]
|
||||||
icon = "png"
|
name = "ras"
|
||||||
|
icon = "generic"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
[exts.exr]
|
[exts.sun]
|
||||||
name = "exr"
|
name = "ras"
|
||||||
icon = "others"
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.ora]
|
||||||
|
name = "ora"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.ani]
|
||||||
|
name = "ani"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.pfm]
|
||||||
|
name = "pfm"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.phm]
|
||||||
|
name = "pfm"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.rgb]
|
||||||
|
name = "rgb"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.rgba]
|
||||||
|
name = "rgb"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.bw]
|
||||||
|
name = "rgb"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
[exts.sgi]
|
||||||
|
name = "rgb"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.pxr]
|
||||||
|
name = "pxr"
|
||||||
|
icon = "generic"
|
||||||
|
behavior = "generic"
|
||||||
|
|
||||||
|
[exts.sct]
|
||||||
|
name = "sct"
|
||||||
|
icon = "generic"
|
||||||
behavior = "generic"
|
behavior = "generic"
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
# Pineapple Picture Association
|
|
||||||
|
|
||||||
TODO
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.20)
|
||||||
|
|
||||||
|
project(qwfassoc_suite LANGUAGES CXX)
|
||||||
|
|
||||||
|
# Qt 6 requires C++17 at minimum.
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
# Let CMake auto-process Q_OBJECT, .ui files and .qrc resources for every
|
||||||
|
# subproject below.
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
# Make the bundled Findwfassoc.cmake module visible to find_package().
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
|
# Qt and LinguistTools are used by both subprojects, so they are looked up at
|
||||||
|
# the top level. The same is true for wfassoc: although only the qwfassoc
|
||||||
|
# library links against it directly, PUBLIC propagation from the library
|
||||||
|
# target makes the dependency available to qwfassoc-standalone as well.
|
||||||
|
# toml11 is only needed when the standalone executable is built, so it is
|
||||||
|
# looked up conditionally below.
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Widgets LinguistTools)
|
||||||
|
find_package(wfassoc REQUIRED)
|
||||||
|
|
||||||
|
# The standalone executable is optional: embedders may want only the library.
|
||||||
|
option(QWFASSOC_BUILD_STANDALONE "Build the qwfassoc-standalone executable" ON)
|
||||||
|
|
||||||
|
add_subdirectory(qwfassoc)
|
||||||
|
|
||||||
|
if(QWFASSOC_BUILD_STANDALONE)
|
||||||
|
find_package(toml11 REQUIRED)
|
||||||
|
add_subdirectory(qwfassoc-standalone)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
# qwfassoc (suite)
|
||||||
|
|
||||||
|
A Qt Widgets based GUI for the [wfassoc](../../wfassoc) library, split into a
|
||||||
|
reusable shared library and a small standalone executable that exercises it.
|
||||||
|
|
||||||
|
The project is organized as two CMake subprojects:
|
||||||
|
|
||||||
|
```
|
||||||
|
qwfassoc/ Parent directory (this README)
|
||||||
|
├── CMakeLists.txt Top-level CMake; add_subdirectory's both
|
||||||
|
│ subprojects and finds Qt, wfassoc, toml11
|
||||||
|
├── cmake/
|
||||||
|
│ ├── Findwfassoc.cmake Verbatim copy of wfassoc's Find module
|
||||||
|
│ └── README.md Provenance notes for the copy
|
||||||
|
├── qwfassoc/ Shared library subproject
|
||||||
|
│ ├── CMakeLists.txt
|
||||||
|
│ ├── i18n/
|
||||||
|
│ │ └── qwfassoc_zh_CN.ts Empty placeholder translation file
|
||||||
|
│ └── src/
|
||||||
|
│ ├── qwfassoc_global.h QWFASSOC_EXPORT macro
|
||||||
|
│ ├── scope.h Shared TargetScope enum
|
||||||
|
│ ├── manifest.h Manifest data struct (no TOML dependency)
|
||||||
|
│ ├── icon_utils.h/.cpp wfassocpp::HICON -> QPixmap conversion
|
||||||
|
│ ├── application_widget.h/.cpp/.ui
|
||||||
|
│ │ Install / uninstall widget
|
||||||
|
│ └── association_widget.h/.cpp/.ui
|
||||||
|
│ File associations widget
|
||||||
|
└── qwfassoc-standalone/ Executable subproject
|
||||||
|
├── CMakeLists.txt
|
||||||
|
├── i18n/
|
||||||
|
│ └── qwfassoc-standalone_zh_CN.ts
|
||||||
|
│ Empty placeholder translation file
|
||||||
|
└── src/
|
||||||
|
├── main.cpp Entry point, CLI parsing, translator loading
|
||||||
|
├── main_window.h/.cpp/.ui
|
||||||
|
│ QDialog hosting the two widgets in a tab widget
|
||||||
|
└── manifest_parser.h/.cpp
|
||||||
|
TOML -> Manifest, Manifest -> Schema
|
||||||
|
```
|
||||||
|
|
||||||
|
## Subprojects at a glance
|
||||||
|
|
||||||
|
### `qwfassoc` (shared library)
|
||||||
|
|
||||||
|
Exports two reusable widgets that wrap wfassoc:
|
||||||
|
|
||||||
|
* `qwfassoc::ApplicationWidget` — install / uninstall the program in the
|
||||||
|
configured scope.
|
||||||
|
* `qwfassoc::AssociationWidget` — stage and apply per-extension link / unlink
|
||||||
|
operations.
|
||||||
|
|
||||||
|
Both widgets follow the **two-phase initialization** pattern expected by Qt
|
||||||
|
Designer promoted widgets: the constructor only takes a `QWidget*` parent and
|
||||||
|
leaves the widget disabled. A `setConfig(Config)` method injects the
|
||||||
|
`wfassocpp::Program` pointer and `TargetScope` (and, for the association
|
||||||
|
widget, whether the OK/Cancel buttons are visible). Each widget also exposes:
|
||||||
|
|
||||||
|
* a `refresh()` slot that re-queries the live wfassoc state, intended to be
|
||||||
|
called by the host when another component has mutated the registry;
|
||||||
|
* a `changed()` signal emitted whenever the widget itself mutates the
|
||||||
|
registry (install / uninstall / apply);
|
||||||
|
* (`AssociationWidget` only) a `finished(bool accepted)` signal emitted when
|
||||||
|
the user clicks OK (after `changed()`) or Cancel, so the host can close the
|
||||||
|
dialog.
|
||||||
|
|
||||||
|
The library also exposes the plain `qwfassoc::Manifest` data struct and a
|
||||||
|
`qwfassoc::icon_utils::fromHicon()` helper, but the TOML parsing logic (which
|
||||||
|
depends on toml11) lives in the standalone executable.
|
||||||
|
|
||||||
|
### `qwfassoc-standalone` (executable)
|
||||||
|
|
||||||
|
Reproduces the original tabbed wfassoc configurator by:
|
||||||
|
|
||||||
|
1. parsing `-c/--manifest <path>` and `-f/--for <user|system>` from the
|
||||||
|
command line;
|
||||||
|
2. building a `wfassocpp::Program` via `parseManifestFile` + `buildSchema`;
|
||||||
|
3. hosting `ApplicationWidget` and `AssociationWidget` inside a `QTabWidget`
|
||||||
|
in a `MainWindow` dialog;
|
||||||
|
4. wiring the widgets' `changed()` and `finished()` signals so that any
|
||||||
|
registry mutation refreshes both pages and OK/Cancel drive the dialog's
|
||||||
|
acceptance.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* **CMake** 3.20 or newer (3.21+ recommended for `qt6_add_translations`).
|
||||||
|
* A C++17 compiler.
|
||||||
|
* **Qt 6** with the `Widgets` and `LinguistTools` components.
|
||||||
|
* **wfassoc**, with `wfassoc_ROOT` pointing at an installed tree (see
|
||||||
|
[`cmake/Findwfassoc.cmake`](cmake/Findwfassoc.cmake) for the expected
|
||||||
|
directory layout).
|
||||||
|
* **toml11** — only required when building the standalone executable.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bat
|
||||||
|
cmake -S . -B build ^
|
||||||
|
-DCMAKE_PREFIX_PATH=C:\Qt\6.x.x\msvc2022_64 ^
|
||||||
|
-Dwfassoc_ROOT=C:\path\to\wfassoc\install ^
|
||||||
|
-Dtoml11_DIR=C:\path\to\toml11\share\toml11\cmake
|
||||||
|
cmake --build build --config Release
|
||||||
|
```
|
||||||
|
|
||||||
|
To skip the standalone executable (and the toml11 dependency):
|
||||||
|
|
||||||
|
```bat
|
||||||
|
cmake -S . -B build -DQWFASSOC_BUILD_STANDALONE=OFF ...
|
||||||
|
```
|
||||||
|
|
||||||
|
The standalone executable is `build/qwfassoc-standalone/Release/qwfassoc-standalone.exe`
|
||||||
|
(or similar, depending on the generator); the library is
|
||||||
|
`build/qwfassoc/Release/qwfassoc.dll`.
|
||||||
|
|
||||||
|
## Running the standalone executable
|
||||||
|
|
||||||
|
| Short | Long | Meaning |
|
||||||
|
| ----- | ------------ | ------------------------------------------------------------------------ |
|
||||||
|
| `-c` | `--manifest` | Path to the application manifest TOML file (see [`example/manifest/ppic.toml`](../manifest/ppic.toml)). |
|
||||||
|
| `-f` | `--for` | Target scope: `user` or `system`. |
|
||||||
|
|
||||||
|
```bat
|
||||||
|
qwfassoc-standalone -c C:\path\to\ppic.toml -f user
|
||||||
|
```
|
||||||
|
|
||||||
|
## Internationalization
|
||||||
|
|
||||||
|
Source strings are English and every user-facing string is wrapped in `tr()`
|
||||||
|
(in code) or is a plain `<string>` element in the `.ui` file (which `uic`
|
||||||
|
wraps in `QCoreApplication::translate`).
|
||||||
|
|
||||||
|
Each subproject ships its own empty placeholder `.ts` file under its
|
||||||
|
`i18n/` directory and registers it with `qt6_add_translations()`:
|
||||||
|
|
||||||
|
* `qwfassoc/i18n/qwfassoc_zh_CN.ts` — covers the library widgets.
|
||||||
|
* `qwfassoc-standalone/i18n/qwfassoc-standalone_zh_CN.ts` — covers the
|
||||||
|
executable-specific messages (CLI errors, tab titles, dialog window
|
||||||
|
title, etc.).
|
||||||
|
|
||||||
|
At runtime, `installTranslators()` in `qwfassoc-standalone/src/main.cpp`
|
||||||
|
loads both `.qm` files for the user's preferred UI language from the
|
||||||
|
`:/i18n/` resource prefix. Translators are expected to fill in the `.ts`
|
||||||
|
files; no actual translation work is performed by the build on its own.
|
||||||
|
|
||||||
|
## Notes and Limitations
|
||||||
|
|
||||||
|
* "Self" detection in the file-association table is based on comparing the
|
||||||
|
display name returned by wfassoc with the display name this program would
|
||||||
|
use. Two programs sharing the exact same display name could therefore be
|
||||||
|
confused.
|
||||||
|
* The system column in `AssociationWidget` is rendered disabled (using
|
||||||
|
`Qt::ItemIsSelectable` without `Qt::ItemIsEnabled`) when `TargetScope` is
|
||||||
|
`User`; the cells stay visible but cannot be clicked.
|
||||||
|
* All errors originating from wfassoc are surfaced through `QMessageBox`
|
||||||
|
dialogs; fatal errors during startup cause the process to exit with a
|
||||||
|
non-zero status code.
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# This module requires the user to set wfassoc_ROOT to the installation
|
# This module requires the user to set wfassoc_ROOT to the installation
|
||||||
# directory of wfassoc. The directory structure under wfassoc_ROOT must be:
|
# directory of wfassoc. The directory structure under wfassoc_ROOT must be:
|
||||||
# bin/ - contains wfassoc_cdylib.dll
|
# bin/ - contains wfassoc_cdylib.dll
|
||||||
# include/ - contains wfassoc.h
|
# include/ - contains wfassoc.h and wfassoc++.h
|
||||||
# lib/ - contains wfassoc_cdylib.dll.lib (import library)
|
# lib/ - contains wfassoc_cdylib.dll.lib (import library)
|
||||||
#
|
#
|
||||||
# This module defines the following variables:
|
# This module defines the following variables:
|
||||||
@@ -36,7 +36,7 @@ set(wfassoc_LIB_DIR ${wfassoc_ROOT}/lib)
|
|||||||
set(wfassoc_BIN_DIR ${wfassoc_ROOT}/bin)
|
set(wfassoc_BIN_DIR ${wfassoc_ROOT}/bin)
|
||||||
|
|
||||||
# Find header files
|
# Find header files
|
||||||
if(EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc.h)
|
if(EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc.h AND EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc++.h)
|
||||||
set(wfassoc_INCLUDE_DIRS ${wfassoc_INCLUDE_DIR})
|
set(wfassoc_INCLUDE_DIRS ${wfassoc_INCLUDE_DIR})
|
||||||
else()
|
else()
|
||||||
message(SEND_ERROR "Missing wfassoc header files in ${wfassoc_INCLUDE_DIR}")
|
message(SEND_ERROR "Missing wfassoc header files in ${wfassoc_INCLUDE_DIR}")
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# qwfassoc/cmake
|
||||||
|
|
||||||
|
This directory holds CMake helper modules used by the `qwfassoc` project.
|
||||||
|
|
||||||
|
## `Findwfassoc.cmake`
|
||||||
|
|
||||||
|
This file is a verbatim copy of the upstream `Findwfassoc.cmake` shipped with
|
||||||
|
the wfassoc C dynamic library, located at:
|
||||||
|
|
||||||
|
```
|
||||||
|
wfassoc-cdylib/cbindgen/Findwfassoc.cmake
|
||||||
|
```
|
||||||
|
|
||||||
|
The copy is committed here so that `qwfassoc` can locate the wfassoc library
|
||||||
|
through a standard `find_package(wfassoc)` call without depending on the source
|
||||||
|
tree layout at configure time.
|
||||||
|
|
||||||
|
To keep this copy in sync with the upstream version, re-run the copy command
|
||||||
|
shown above whenever `wfassoc-cdylib/cbindgen/Findwfassoc.cmake` is updated.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# qwfassoc-standalone: executable that uses the qwfassoc library to reproduce
|
||||||
|
# the original tabbed wfassoc configurator.
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
set(QWFASSOC_STANDALONE_SOURCES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest_parser.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_STANDALONE_HEADERS
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest_parser.h"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_STANDALONE_UI
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/main_window.ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(qwfassoc-standalone WIN32
|
||||||
|
${QWFASSOC_STANDALONE_SOURCES}
|
||||||
|
${QWFASSOC_STANDALONE_HEADERS}
|
||||||
|
${QWFASSOC_STANDALONE_UI}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(qwfassoc-standalone PRIVATE
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(qwfassoc-standalone PRIVATE
|
||||||
|
qwfassoc
|
||||||
|
Qt6::Widgets
|
||||||
|
toml11::toml11
|
||||||
|
)
|
||||||
|
|
||||||
|
# Translation pipeline for the standalone executable. The library's strings
|
||||||
|
# are translated by qwfassoc's own .ts file; this one only covers the
|
||||||
|
# executable-specific messages (CLI errors, tab titles, etc.).
|
||||||
|
set(QWFASSOC_STANDALONE_TS_FILES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/i18n/qwfassoc-standalone_zh_CN.ts"
|
||||||
|
)
|
||||||
|
|
||||||
|
qt6_add_translations(qwfassoc-standalone
|
||||||
|
TS_FILES ${QWFASSOC_STANDALONE_TS_FILES}
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="zh_CN">
|
||||||
|
</TS>
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
#include <QApplication>
|
||||||
|
#include <QCommandLineOption>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QTranslator>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "main_window.h"
|
||||||
|
#include "manifest_parser.h"
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Context used for translatable strings that live outside of any QObject.
|
||||||
|
constexpr const char* kTranslationContext = "qwfassoc-standalone";
|
||||||
|
|
||||||
|
// Show a modal error dialog with the given message and return a non-zero
|
||||||
|
// exit code. Used for the various fatal conditions that may occur before the
|
||||||
|
// main dialog can be shown.
|
||||||
|
int fatal(QWidget* parent, const QString& message) {
|
||||||
|
QMessageBox::critical(parent, QApplication::applicationName(), message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the --for command line value to a TargetScope. Throws if the value
|
||||||
|
// is not one of the accepted strings.
|
||||||
|
qwfassoc::TargetScope parseScope(const QString& value) {
|
||||||
|
const QString normalized = value.trimmed().toLower();
|
||||||
|
if (normalized == QStringLiteral("user")) {
|
||||||
|
return qwfassoc::TargetScope::User;
|
||||||
|
}
|
||||||
|
if (normalized == QStringLiteral("system")) {
|
||||||
|
return qwfassoc::TargetScope::System;
|
||||||
|
}
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Invalid value for --for. Use \"user\" or \"system\".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Install the translation(s) matching the user's preferred UI language, if
|
||||||
|
// any. Both the qwfassoc library's .qm and this executable's .qm are loaded
|
||||||
|
// (their .ts files live under the per-project i18n/ directories and are
|
||||||
|
// embedded under the ":/i18n" resource prefix by qt6_add_translations).
|
||||||
|
void installTranslators(QApplication& app) {
|
||||||
|
const QStringList uiLanguages = QLocale::system().uiLanguages();
|
||||||
|
for (const QString& locale : uiLanguages) {
|
||||||
|
const QString name = QLocale(locale).name();
|
||||||
|
|
||||||
|
QTranslator* libTranslator = new QTranslator(&app);
|
||||||
|
if (libTranslator->load(QStringLiteral(":/i18n/qwfassoc_") + name)) {
|
||||||
|
app.installTranslator(libTranslator);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTranslator* appTranslator = new QTranslator(&app);
|
||||||
|
if (appTranslator->load(
|
||||||
|
QStringLiteral(":/i18n/qwfassoc-standalone_") + name)) {
|
||||||
|
app.installTranslator(appTranslator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
QApplication::setApplicationName(QStringLiteral("qwfassoc-standalone"));
|
||||||
|
|
||||||
|
// Install available translations before any translatable string is
|
||||||
|
// resolved so that tr() and QCoreApplication::translate() pick up the
|
||||||
|
// right language.
|
||||||
|
installTranslators(app);
|
||||||
|
|
||||||
|
QApplication::setApplicationDisplayName(
|
||||||
|
QCoreApplication::translate(kTranslationContext,
|
||||||
|
"qwfassoc - wfassoc Configurator"));
|
||||||
|
|
||||||
|
// Parse command line arguments using Qt's built-in parser.
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.setApplicationDescription(
|
||||||
|
QCoreApplication::translate(
|
||||||
|
kTranslationContext,
|
||||||
|
"Qt-based GUI executable for the wfassoc library."));
|
||||||
|
parser.addHelpOption();
|
||||||
|
|
||||||
|
QCommandLineOption manifestOption(
|
||||||
|
QStringList() << QStringLiteral("c") << QStringLiteral("manifest"),
|
||||||
|
QCoreApplication::translate(kTranslationContext,
|
||||||
|
"Path to the application manifest TOML file."),
|
||||||
|
QStringLiteral("manifest"));
|
||||||
|
QCommandLineOption forOption(
|
||||||
|
QStringList() << QStringLiteral("f") << QStringLiteral("for"),
|
||||||
|
QCoreApplication::translate(kTranslationContext,
|
||||||
|
"Target scope: \"user\" or \"system\"."),
|
||||||
|
QStringLiteral("scope"));
|
||||||
|
|
||||||
|
parser.addOption(manifestOption);
|
||||||
|
parser.addOption(forOption);
|
||||||
|
parser.process(app);
|
||||||
|
|
||||||
|
// Validate that both mandatory options were provided with sane values.
|
||||||
|
const QString manifestPath = parser.value(manifestOption);
|
||||||
|
const QString forValue = parser.value(forOption);
|
||||||
|
|
||||||
|
if (manifestPath.isEmpty()) {
|
||||||
|
return fatal(nullptr,
|
||||||
|
QCoreApplication::translate(
|
||||||
|
kTranslationContext,
|
||||||
|
"The --manifest/-c option is required."));
|
||||||
|
}
|
||||||
|
if (forValue.isEmpty()) {
|
||||||
|
return fatal(nullptr,
|
||||||
|
QCoreApplication::translate(
|
||||||
|
kTranslationContext,
|
||||||
|
"The --for/-f option is required."));
|
||||||
|
}
|
||||||
|
|
||||||
|
qwfassoc::TargetScope scope;
|
||||||
|
try {
|
||||||
|
scope = parseScope(forValue);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
return fatal(nullptr, QString::fromUtf8(e.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the wfassoc runtime. WFStartup must run before most other
|
||||||
|
// wfassoc calls; if it fails we cannot proceed.
|
||||||
|
if (!wfassoc::WFStartup()) {
|
||||||
|
return fatal(nullptr,
|
||||||
|
QString::fromUtf8(wfassoc::WFGetLastError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the manifest -> schema -> program pipeline and run the dialog.
|
||||||
|
// Program construction consumes the schema (move) and performs the deep
|
||||||
|
// validation (identifier format, dangling references, etc.).
|
||||||
|
int exitCode = 0;
|
||||||
|
try {
|
||||||
|
qwfassoc::Manifest manifest =
|
||||||
|
qwfassoc::parseManifestFile(manifestPath.toStdString());
|
||||||
|
wfassocpp::Schema schema = qwfassoc::buildSchema(manifest);
|
||||||
|
wfassocpp::Program program(std::move(schema));
|
||||||
|
|
||||||
|
qwfassoc::MainWindow window(std::move(program), scope);
|
||||||
|
exitCode = window.exec();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
wfassoc::WFShutdown();
|
||||||
|
return fatal(nullptr, QString::fromUtf8(e.what()));
|
||||||
|
}
|
||||||
|
|
||||||
|
wfassoc::WFShutdown();
|
||||||
|
return exitCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#include "main_window.h"
|
||||||
|
#include "ui_main_window.h"
|
||||||
|
|
||||||
|
#include "application_widget.h"
|
||||||
|
#include "association_widget.h"
|
||||||
|
#include "icon_utils.h"
|
||||||
|
|
||||||
|
#include <QTabWidget>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
MainWindow::MainWindow(wfassocpp::Program program,
|
||||||
|
TargetScope scope,
|
||||||
|
QWidget* parent)
|
||||||
|
: QDialog(parent),
|
||||||
|
ui_(new Ui::MainWindow),
|
||||||
|
appTab_(nullptr),
|
||||||
|
assocTab_(nullptr),
|
||||||
|
program_(std::move(program)),
|
||||||
|
scope_(scope) {
|
||||||
|
ui_->setupUi(this);
|
||||||
|
|
||||||
|
// Resolve program metadata that several labels depend on.
|
||||||
|
programName_ = QString::fromUtf8(program_.ResolveName());
|
||||||
|
|
||||||
|
{
|
||||||
|
auto iconRc = program_.ResolveIcon();
|
||||||
|
auto handle = iconRc.GetIcon();
|
||||||
|
programIcon_ = icon_utils::fromHicon(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compose the window title and icon.
|
||||||
|
setWindowTitle(tr("%1 Options").arg(programName_));
|
||||||
|
if (!programIcon_.isNull()) {
|
||||||
|
setWindowIcon(QIcon(programIcon_));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the two tab pages from the library widgets.
|
||||||
|
appTab_ = new ApplicationWidget(this);
|
||||||
|
assocTab_ = new AssociationWidget(this);
|
||||||
|
|
||||||
|
ui_->tabWidget->addTab(appTab_, tr("Applications"));
|
||||||
|
ui_->tabWidget->addTab(assocTab_, tr("File Associations"));
|
||||||
|
|
||||||
|
// Two-phase initialization. The standalone executable wants the OK and
|
||||||
|
// Cancel buttons visible because they drive dialog acceptance.
|
||||||
|
appTab_->setConfig({&program_, scope_});
|
||||||
|
assocTab_->setConfig({&program_, scope_, /*showOkCancelButtons=*/true});
|
||||||
|
|
||||||
|
// Wire widget signals so that any wfassoc change refreshes both pages,
|
||||||
|
// and the association widget can request dialog closure.
|
||||||
|
connect(appTab_, &ApplicationWidget::changed, this,
|
||||||
|
&MainWindow::onAnyChanged);
|
||||||
|
connect(assocTab_, &AssociationWidget::changed, this,
|
||||||
|
&MainWindow::onAnyChanged);
|
||||||
|
connect(assocTab_, &AssociationWidget::finished, this,
|
||||||
|
&MainWindow::onFinished);
|
||||||
|
}
|
||||||
|
|
||||||
|
MainWindow::~MainWindow() = default;
|
||||||
|
|
||||||
|
void MainWindow::onAnyChanged() {
|
||||||
|
if (appTab_ != nullptr) {
|
||||||
|
appTab_->refresh();
|
||||||
|
}
|
||||||
|
if (assocTab_ != nullptr) {
|
||||||
|
assocTab_->refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onFinished(bool accepted) {
|
||||||
|
if (accepted) {
|
||||||
|
accept();
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||||
|
#define QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class MainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
class ApplicationWidget;
|
||||||
|
class AssociationWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Top-level dialog used by the qwfassoc-standalone executable. Hosts the two
|
||||||
|
// reusable widgets from the qwfassoc library inside a QTabWidget and wires
|
||||||
|
// their changed() / finished() signals together.
|
||||||
|
class MainWindow : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit MainWindow(wfassocpp::Program program,
|
||||||
|
TargetScope scope,
|
||||||
|
QWidget* parent = nullptr);
|
||||||
|
~MainWindow() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// Called whenever one of the embedded widgets reports that wfassoc state
|
||||||
|
// has changed. Refreshes both widgets so they stay in sync.
|
||||||
|
void onAnyChanged();
|
||||||
|
// Called when the association widget asks the dialog to close.
|
||||||
|
void onFinished(bool accepted);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::MainWindow* ui_;
|
||||||
|
ApplicationWidget* appTab_;
|
||||||
|
AssociationWidget* assocTab_;
|
||||||
|
|
||||||
|
wfassocpp::Program program_;
|
||||||
|
TargetScope scope_;
|
||||||
|
|
||||||
|
QString programName_;
|
||||||
|
QPixmap programIcon_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_STANDALONE_MAIN_WINDOW_H_
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QDialog" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>480</width>
|
||||||
|
<height>600</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="minimumSize">
|
||||||
|
<size>
|
||||||
|
<width>480</width>
|
||||||
|
<height>600</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="maximumSize">
|
||||||
|
<size>
|
||||||
|
<width>480</width>
|
||||||
|
<height>600</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Options</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="mainLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>9</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QTabWidget" name="tabWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#include "manifest_parser.h"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <toml.hpp>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// region: TOML Parsing
|
||||||
|
|
||||||
|
Manifest parseManifestFile(const std::string& path) {
|
||||||
|
toml::value root;
|
||||||
|
try {
|
||||||
|
root = toml::parse(path);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
// toml::parse already produces a descriptive message including file
|
||||||
|
// path and line number; just propagate it wrapped for context.
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::string("Failed to parse manifest TOML file: ") + e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
Manifest manifest;
|
||||||
|
|
||||||
|
// Helper lambda: read a string field, re-throwing with a clearer message.
|
||||||
|
auto readString = [](const toml::value& v,
|
||||||
|
const std::string& key) -> std::string {
|
||||||
|
try {
|
||||||
|
return toml::find<std::string>(v, key);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Manifest field \"" + key +
|
||||||
|
"\" is missing or is not a string: " + e.what());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Required top-level scalar fields.
|
||||||
|
manifest.identifier = readString(root, "identifier");
|
||||||
|
manifest.path = readString(root, "path");
|
||||||
|
manifest.clsid = readString(root, "clsid");
|
||||||
|
|
||||||
|
// Optional top-level scalar fields.
|
||||||
|
if (root.contains("name")) {
|
||||||
|
manifest.name = readString(root, "name");
|
||||||
|
}
|
||||||
|
if (root.contains("icon")) {
|
||||||
|
manifest.icon = readString(root, "icon");
|
||||||
|
}
|
||||||
|
if (root.contains("behavior")) {
|
||||||
|
manifest.behavior = readString(root, "behavior");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper lambda: copy a TOML table of {string -> string} into a std::map.
|
||||||
|
auto readStringTable =
|
||||||
|
[](const toml::value& parent,
|
||||||
|
const std::string& key) -> std::map<std::string, std::string> {
|
||||||
|
if (!parent.contains(key)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::map<std::string, std::string> out;
|
||||||
|
try {
|
||||||
|
// Keep the sub-value alive as a local so that the table reference
|
||||||
|
// obtained from as_table() stays valid for the loop below,
|
||||||
|
// regardless of whether toml::find returns by reference or by
|
||||||
|
// value in the toml11 version that is linked.
|
||||||
|
const toml::value& sub = toml::find(parent, key);
|
||||||
|
for (const auto& [k, v] : sub.as_table()) {
|
||||||
|
out.emplace(k, v.as_string());
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Manifest table \"" + key +
|
||||||
|
"\" contains an invalid entry: " + e.what());
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
manifest.strs = readStringTable(root, "strs");
|
||||||
|
manifest.icons = readStringTable(root, "icons");
|
||||||
|
manifest.behaviors = readStringTable(root, "behaviors");
|
||||||
|
|
||||||
|
// Extension table. Each entry is itself a table with name/icon/behavior.
|
||||||
|
if (root.contains("exts")) {
|
||||||
|
try {
|
||||||
|
const toml::value& exts_value = toml::find(root, "exts");
|
||||||
|
for (const auto& [ext_key, ext_value] : exts_value.as_table()) {
|
||||||
|
ManifestExt ext;
|
||||||
|
ext.name = readString(ext_value, "name");
|
||||||
|
ext.icon = readString(ext_value, "icon");
|
||||||
|
ext.behavior = readString(ext_value, "behavior");
|
||||||
|
manifest.exts.emplace(ext_key, std::move(ext));
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Manifest \"exts\" table contains an invalid entry: " +
|
||||||
|
e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Schema Conversion
|
||||||
|
|
||||||
|
wfassocpp::Schema buildSchema(const Manifest& manifest) {
|
||||||
|
wfassocpp::Schema schema;
|
||||||
|
|
||||||
|
// The wfassocpp wrappers translate any underlying failure into a
|
||||||
|
// std::runtime_error via _Check, so we let those propagate untouched.
|
||||||
|
schema.SetIdentifier(manifest.identifier.c_str());
|
||||||
|
schema.SetPath(manifest.path.c_str());
|
||||||
|
schema.SetClsid(manifest.clsid.c_str());
|
||||||
|
|
||||||
|
// Optional fields: passing nullptr tells wfassoc to clear the value.
|
||||||
|
schema.SetName(manifest.name.has_value() ? manifest.name->c_str()
|
||||||
|
: nullptr);
|
||||||
|
schema.SetIcon(manifest.icon.has_value() ? manifest.icon->c_str()
|
||||||
|
: nullptr);
|
||||||
|
schema.SetBehavior(manifest.behavior.has_value() ? manifest.behavior->c_str()
|
||||||
|
: nullptr);
|
||||||
|
|
||||||
|
for (const auto& [key, value] : manifest.strs) {
|
||||||
|
schema.AddStr(key.c_str(), value.c_str());
|
||||||
|
}
|
||||||
|
for (const auto& [key, value] : manifest.icons) {
|
||||||
|
schema.AddIcon(key.c_str(), value.c_str());
|
||||||
|
}
|
||||||
|
for (const auto& [key, value] : manifest.behaviors) {
|
||||||
|
schema.AddBehavior(key.c_str(), value.c_str());
|
||||||
|
}
|
||||||
|
for (const auto& [key, value] : manifest.exts) {
|
||||||
|
schema.AddExt(key.c_str(),
|
||||||
|
value.name.c_str(),
|
||||||
|
value.icon.c_str(),
|
||||||
|
value.behavior.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||||
|
#define QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "manifest.h"
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Parse a manifest TOML file from disk into a Manifest value.
|
||||||
|
// Throws std::runtime_error on any IO or TOML syntax error.
|
||||||
|
Manifest parseManifestFile(const std::string& path);
|
||||||
|
|
||||||
|
// Build a wfassocpp::Schema from a manifest value.
|
||||||
|
// Throws std::runtime_error (originating from wfassocpp::_Check) when the
|
||||||
|
// wfassoc library rejects an operation, e.g. on duplicate keys or dangling
|
||||||
|
// references.
|
||||||
|
wfassocpp::Schema buildSchema(const Manifest& manifest);
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_STANDALONE_MANIFEST_PARSER_H_
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
# qwfassoc: shared library exporting reusable Qt widgets that wrap wfassoc.
|
||||||
|
|
||||||
|
# Qt 6 requires C++17 at minimum.
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
|
set(QWFASSOC_SOURCES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/application_widget.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/association_widget.cpp"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/icon_utils.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_HEADERS
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/qwfassoc_global.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/scope.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/manifest.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/application_widget.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/association_widget.h"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/icon_utils.h"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(QWFASSOC_UI
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/application_widget.ui"
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src/association_widget.ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(qwfassoc SHARED
|
||||||
|
${QWFASSOC_SOURCES}
|
||||||
|
${QWFASSOC_HEADERS}
|
||||||
|
${QWFASSOC_UI}
|
||||||
|
)
|
||||||
|
|
||||||
|
# QWFASSOC_LIBRARY switches QWFASSOC_EXPORT from import to export mode.
|
||||||
|
target_compile_definitions(qwfassoc PRIVATE QWFASSOC_LIBRARY)
|
||||||
|
|
||||||
|
# Consumers (and the library itself) need to find the public headers under
|
||||||
|
# src/. PUBLIC propagates the include path to anyone linking against qwfassoc.
|
||||||
|
target_include_directories(qwfassoc PUBLIC
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(qwfassoc PUBLIC
|
||||||
|
Qt6::Widgets
|
||||||
|
wfassoc::wfassoc
|
||||||
|
)
|
||||||
|
|
||||||
|
# Translation pipeline for the library. qt6_add_translations() runs lupdate
|
||||||
|
# against the target's sources and embeds the lrelease output under the
|
||||||
|
# ":/i18n" resource prefix, where installTranslators() in the executable
|
||||||
|
# looks it up at runtime.
|
||||||
|
set(QWFASSOC_TS_FILES
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/i18n/qwfassoc_zh_CN.ts"
|
||||||
|
)
|
||||||
|
|
||||||
|
qt6_add_translations(qwfassoc
|
||||||
|
TS_FILES ${QWFASSOC_TS_FILES}
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="zh_CN">
|
||||||
|
</TS>
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#include "application_widget.h"
|
||||||
|
#include "ui_application_widget.h"
|
||||||
|
#include "icon_utils.h"
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Convert the library's TargetScope enum to the wfassocpp::Scope value used by
|
||||||
|
// the program APIs (register / unregister / link / unlink / is_registered).
|
||||||
|
wfassocpp::Scope toWfassocScope(TargetScope scope) {
|
||||||
|
return scope == TargetScope::User ? wfassocpp::Scope::User
|
||||||
|
: wfassocpp::Scope::System;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ApplicationWidget::ApplicationWidget(QWidget* parent)
|
||||||
|
: QWidget(parent), ui_(new Ui::ApplicationWidget) {
|
||||||
|
ui_->setupUi(this);
|
||||||
|
|
||||||
|
connect(ui_->installButton, &QPushButton::clicked, this,
|
||||||
|
&ApplicationWidget::onInstallClicked);
|
||||||
|
connect(ui_->uninstallButton, &QPushButton::clicked, this,
|
||||||
|
&ApplicationWidget::onUninstallClicked);
|
||||||
|
|
||||||
|
// Until setConfig() is called we have no program to operate on; keep the
|
||||||
|
// whole widget disabled.
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationWidget::~ApplicationWidget() = default;
|
||||||
|
|
||||||
|
void ApplicationWidget::setConfig(const Config& config) {
|
||||||
|
program_ = config.program;
|
||||||
|
scope_ = config.scope;
|
||||||
|
|
||||||
|
if (program_ != nullptr) {
|
||||||
|
// Resolve program metadata that several labels depend on. These calls
|
||||||
|
// may throw std::runtime_error on failure; the caller is expected to
|
||||||
|
// wrap setConfig() in a try/catch and present an error dialog.
|
||||||
|
programName_ = QString::fromUtf8(program_->ResolveName());
|
||||||
|
|
||||||
|
auto iconRc = program_->ResolveIcon();
|
||||||
|
auto handle = iconRc.GetIcon();
|
||||||
|
programIcon_ = icon_utils::fromHicon(handle);
|
||||||
|
|
||||||
|
if (!programIcon_.isNull()) {
|
||||||
|
ui_->appIconLabel->setPixmap(
|
||||||
|
programIcon_.scaled(32, 32, Qt::KeepAspectRatio,
|
||||||
|
Qt::SmoothTransformation));
|
||||||
|
}
|
||||||
|
ui_->appDescLabel->setText(
|
||||||
|
tr("Install or uninstall %1 here.").arg(programName_));
|
||||||
|
|
||||||
|
setEnabled(true);
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
programName_.clear();
|
||||||
|
programIcon_ = QPixmap();
|
||||||
|
ui_->appIconLabel->setPixmap(QPixmap());
|
||||||
|
ui_->appIconLabel->setText(QString());
|
||||||
|
ui_->appDescLabel->setText(QString());
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationWidget::refresh() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
ui_->installButton->setEnabled(false);
|
||||||
|
ui_->uninstallButton->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool registered = program_->IsRegistered(toWfassocScope(scope_));
|
||||||
|
ui_->installButton->setEnabled(!registered);
|
||||||
|
ui_->uninstallButton->setEnabled(registered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationWidget::onInstallClicked() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
program_->Register(toWfassocScope(scope_));
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::information(this, tr("Information"),
|
||||||
|
tr("Application installed successfully."));
|
||||||
|
refresh();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplicationWidget::onUninstallClicked() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
program_->Unregister(toWfassocScope(scope_));
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::information(this, tr("Information"),
|
||||||
|
tr("Application uninstalled successfully."));
|
||||||
|
refresh();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_APPLICATION_WIDGET_H_
|
||||||
|
#define QWFASSOC_APPLICATION_WIDGET_H_
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "qwfassoc_global.h"
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class ApplicationWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Widget exposing install / uninstall actions for a single wfassoc program.
|
||||||
|
//
|
||||||
|
// The widget follows the two-phase initialization pattern required by Qt
|
||||||
|
// Designer promoted widgets: the constructor only takes a parent, and the
|
||||||
|
// caller must invoke setConfig() with the target program and scope before the
|
||||||
|
// widget becomes usable. Until setConfig() is called the widget is disabled.
|
||||||
|
class QWFASSOC_EXPORT ApplicationWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Configuration bundle passed to setConfig().
|
||||||
|
struct Config {
|
||||||
|
// Non-owning pointer to the wfassoc program. Must outlive the widget.
|
||||||
|
wfassocpp::Program* program = nullptr;
|
||||||
|
// Scope that install/unregister operations apply to.
|
||||||
|
TargetScope scope = TargetScope::User;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ApplicationWidget(QWidget* parent = nullptr);
|
||||||
|
~ApplicationWidget() override;
|
||||||
|
|
||||||
|
// Two-phase initialization. Calling this with a non-null program enables
|
||||||
|
// the widget and triggers an initial refresh. Calling it with a null
|
||||||
|
// program (or not calling it at all) leaves the widget disabled.
|
||||||
|
void setConfig(const Config& config);
|
||||||
|
|
||||||
|
// Re-query the live wfassoc state and update the enabled state of the
|
||||||
|
// install / uninstall buttons. Called automatically by setConfig() and
|
||||||
|
// also intended to be called by the host whenever another component has
|
||||||
|
// mutated wfassoc state.
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Emitted whenever the user performs an action that mutates wfassoc
|
||||||
|
// state (i.e. install or uninstall). The host should refresh every
|
||||||
|
// widget that depends on wfassoc state in response.
|
||||||
|
void changed();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onInstallClicked();
|
||||||
|
void onUninstallClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::ApplicationWidget* ui_;
|
||||||
|
wfassocpp::Program* program_ = nullptr;
|
||||||
|
TargetScope scope_ = TargetScope::User;
|
||||||
|
|
||||||
|
// Cached metadata used to fill the widget labels.
|
||||||
|
QString programName_;
|
||||||
|
QPixmap programIcon_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_APPLICATION_WIDGET_H_
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>ApplicationWidget</class>
|
||||||
|
<widget class="QWidget" name="ApplicationWidget">
|
||||||
|
<layout class="QVBoxLayout" name="mainLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="groupBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Install and Uninstall</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="groupLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="headerLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="appIconLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">[icon]</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="appDescLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>Install or uninstall the application here.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="actionLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="installButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Install</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="uninstallButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Uninstall</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -0,0 +1,438 @@
|
|||||||
|
#include "association_widget.h"
|
||||||
|
#include "ui_association_widget.h"
|
||||||
|
#include "icon_utils.h"
|
||||||
|
|
||||||
|
#include <QHeaderView>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QProcessEnvironment>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTableWidgetItem>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Convert the library's TargetScope enum to the wfassocpp::Scope value used by
|
||||||
|
// the program APIs (register / unregister / link / unlink / is_registered).
|
||||||
|
wfassocpp::Scope toWfassocScope(TargetScope scope) {
|
||||||
|
return scope == TargetScope::User ? wfassocpp::Scope::User
|
||||||
|
: wfassocpp::Scope::System;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide which CellState a queried ExtStatus corresponds to, given the
|
||||||
|
// resolved "self" name for this extension. We treat the cell as Self when the
|
||||||
|
// resolved display name matches our own; otherwise it is treated as Other.
|
||||||
|
detail::CellState classifyCell(const QString& selfName,
|
||||||
|
const QString& observedName) {
|
||||||
|
return observedName == selfName ? detail::CellState::Self
|
||||||
|
: detail::CellState::Other;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Effective icon to draw for a cell, based on its state.
|
||||||
|
QPixmap effectiveCellIcon(const detail::ExtRow& row,
|
||||||
|
const detail::CellData& cell) {
|
||||||
|
using detail::CellState;
|
||||||
|
switch (cell.state) {
|
||||||
|
case CellState::Blank:
|
||||||
|
return QPixmap();
|
||||||
|
case CellState::Self:
|
||||||
|
return row.selfIcon;
|
||||||
|
case CellState::Other:
|
||||||
|
return cell.icon;
|
||||||
|
}
|
||||||
|
return QPixmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
AssociationWidget::AssociationWidget(QWidget* parent)
|
||||||
|
: QWidget(parent), ui_(new Ui::AssociationWidget) {
|
||||||
|
ui_->setupUi(this);
|
||||||
|
|
||||||
|
// Fetch the current user name for the second column header. The USERNAME
|
||||||
|
// environment variable is good enough on Windows; fall back to a static
|
||||||
|
// translatable placeholder if it is unset for any reason.
|
||||||
|
userName_ = QProcessEnvironment::systemEnvironment().value(
|
||||||
|
QStringLiteral("USERNAME"), tr("User"));
|
||||||
|
|
||||||
|
// Give the table as much vertical room as possible inside its layout.
|
||||||
|
ui_->mainLayout->setStretch(2, 1);
|
||||||
|
|
||||||
|
// Reasonable default column widths so dotted extensions and ProgId names
|
||||||
|
// stay readable in the 480px dialog the widget typically lives in.
|
||||||
|
ui_->assocTable->setColumnWidth(0, 90);
|
||||||
|
ui_->assocTable->setColumnWidth(1, 175);
|
||||||
|
ui_->assocTable->setColumnWidth(2, 175);
|
||||||
|
ui_->assocTable->verticalHeader()->setVisible(false);
|
||||||
|
ui_->assocTable->setShowGrid(true);
|
||||||
|
|
||||||
|
connect(ui_->selectUserButton, &QPushButton::clicked, this,
|
||||||
|
&AssociationWidget::onSelectUserClicked);
|
||||||
|
connect(ui_->selectSystemButton, &QPushButton::clicked, this,
|
||||||
|
&AssociationWidget::onSelectSystemClicked);
|
||||||
|
connect(ui_->assocTable, &QTableWidget::cellClicked, this,
|
||||||
|
&AssociationWidget::onCellClicked);
|
||||||
|
connect(ui_->okButton, &QPushButton::clicked, this,
|
||||||
|
&AssociationWidget::onOkClicked);
|
||||||
|
connect(ui_->cancelButton, &QPushButton::clicked, this,
|
||||||
|
&AssociationWidget::onCancelClicked);
|
||||||
|
connect(ui_->applyButton, &QPushButton::clicked, this,
|
||||||
|
&AssociationWidget::onApplyClicked);
|
||||||
|
|
||||||
|
// Until setConfig() is called we have no program to operate on; keep the
|
||||||
|
// whole widget disabled.
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssociationWidget::~AssociationWidget() = default;
|
||||||
|
|
||||||
|
void AssociationWidget::setConfig(const Config& config) {
|
||||||
|
program_ = config.program;
|
||||||
|
scope_ = config.scope;
|
||||||
|
showOkCancelButtons_ = config.showOkCancelButtons;
|
||||||
|
|
||||||
|
ui_->okButton->setVisible(showOkCancelButtons_);
|
||||||
|
ui_->cancelButton->setVisible(showOkCancelButtons_);
|
||||||
|
|
||||||
|
if (program_ != nullptr) {
|
||||||
|
programName_ = QString::fromUtf8(program_->ResolveName());
|
||||||
|
ui_->assocHeaderLabel->setText(
|
||||||
|
tr("File types associated with %1:").arg(programName_));
|
||||||
|
|
||||||
|
QStringList headers;
|
||||||
|
headers << tr("Type") << userName_ << tr("All Users");
|
||||||
|
ui_->assocTable->setHorizontalHeaderLabels(headers);
|
||||||
|
|
||||||
|
setEnabled(true);
|
||||||
|
refresh();
|
||||||
|
} else {
|
||||||
|
programName_.clear();
|
||||||
|
ui_->assocHeaderLabel->setText(QString());
|
||||||
|
ui_->assocTable->setRowCount(0);
|
||||||
|
rows_.clear();
|
||||||
|
setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::refresh() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateEnabledState();
|
||||||
|
rebuildTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::updateEnabledState() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
ui_->selectUserButton->setEnabled(false);
|
||||||
|
ui_->selectSystemButton->setEnabled(false);
|
||||||
|
ui_->assocTable->setEnabled(false);
|
||||||
|
ui_->applyButton->setEnabled(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool registered = program_->IsRegistered(toWfassocScope(scope_));
|
||||||
|
const bool userColumnActive = registered;
|
||||||
|
const bool systemColumnActive = registered && isSystemColumnEnabled();
|
||||||
|
|
||||||
|
ui_->selectUserButton->setEnabled(userColumnActive);
|
||||||
|
ui_->selectSystemButton->setEnabled(systemColumnActive);
|
||||||
|
ui_->assocTable->setEnabled(registered);
|
||||||
|
// The Apply button enable state is driven by pending changes too; only
|
||||||
|
// touch it here to make sure it's disabled when nothing is registered.
|
||||||
|
if (!registered) {
|
||||||
|
ui_->applyButton->setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::rebuildTable() {
|
||||||
|
if (program_ == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshing_ = true;
|
||||||
|
|
||||||
|
rows_.clear();
|
||||||
|
const size_t count = program_->ExtsLen();
|
||||||
|
rows_.reserve(count);
|
||||||
|
|
||||||
|
ui_->assocTable->setRowCount(static_cast<int>(count));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
detail::ExtRow row;
|
||||||
|
row.index = i;
|
||||||
|
|
||||||
|
// Self extension info: dotted body, display name and cached icon.
|
||||||
|
auto selfExt = program_->ResolveExt(i);
|
||||||
|
row.extBody = QString::fromUtf8(selfExt.GetExt());
|
||||||
|
row.dottedExt = QString::fromUtf8(selfExt.GetDottedExt());
|
||||||
|
row.selfName = QString::fromUtf8(selfExt.GetName());
|
||||||
|
row.selfIcon = icon_utils::fromHicon(selfExt.GetIcon());
|
||||||
|
|
||||||
|
// Query the user-view and system-view states. None means blank;
|
||||||
|
// a match against our self name means Self; anything else is Other
|
||||||
|
// and we keep the original name/icon around for display.
|
||||||
|
auto userStatus = program_->QueryExt(wfassocpp::View::User, i);
|
||||||
|
if (userStatus) {
|
||||||
|
const QString observedName =
|
||||||
|
QString::fromUtf8(userStatus->GetName());
|
||||||
|
row.initialUser.state = classifyCell(row.selfName, observedName);
|
||||||
|
row.initialUser.name = observedName;
|
||||||
|
row.initialUser.icon =
|
||||||
|
icon_utils::fromHicon(userStatus->GetIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto systemStatus = program_->QueryExt(wfassocpp::View::System, i);
|
||||||
|
if (systemStatus) {
|
||||||
|
const QString observedName =
|
||||||
|
QString::fromUtf8(systemStatus->GetName());
|
||||||
|
row.initialSystem.state =
|
||||||
|
classifyCell(row.selfName, observedName);
|
||||||
|
row.initialSystem.name = observedName;
|
||||||
|
row.initialSystem.icon =
|
||||||
|
icon_utils::fromHicon(systemStatus->GetIcon());
|
||||||
|
}
|
||||||
|
|
||||||
|
row.pendingUser = row.initialUser;
|
||||||
|
row.pendingSystem = row.initialSystem;
|
||||||
|
|
||||||
|
rows_.push_back(std::move(row));
|
||||||
|
|
||||||
|
// Create the QTableWidgetItem cells once; subsequent refreshes only
|
||||||
|
// update their text/icon and flags.
|
||||||
|
const int rowIdx = static_cast<int>(i);
|
||||||
|
|
||||||
|
auto* typeItem = new QTableWidgetItem(rows_.back().dottedExt);
|
||||||
|
ui_->assocTable->setItem(rowIdx, 0, typeItem);
|
||||||
|
|
||||||
|
auto* userItem = new QTableWidgetItem;
|
||||||
|
userItem->setTextAlignment(Qt::AlignCenter);
|
||||||
|
ui_->assocTable->setItem(rowIdx, 1, userItem);
|
||||||
|
|
||||||
|
auto* systemItem = new QTableWidgetItem;
|
||||||
|
systemItem->setTextAlignment(Qt::AlignCenter);
|
||||||
|
ui_->assocTable->setItem(rowIdx, 2, systemItem);
|
||||||
|
|
||||||
|
refreshRowDisplay(rowIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshing_ = false;
|
||||||
|
|
||||||
|
updateApplyButtonEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::refreshRowDisplay(int row) {
|
||||||
|
using detail::CellState;
|
||||||
|
|
||||||
|
if (row < 0 || row >= static_cast<int>(rows_.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const detail::ExtRow& r = rows_[row];
|
||||||
|
|
||||||
|
// Column 0: hybrid icon (user-preferred) + dotted extension.
|
||||||
|
QPixmap hybridIcon;
|
||||||
|
if (r.pendingUser.state != CellState::Blank) {
|
||||||
|
hybridIcon = effectiveCellIcon(r, r.pendingUser);
|
||||||
|
} else if (r.pendingSystem.state != CellState::Blank) {
|
||||||
|
hybridIcon = effectiveCellIcon(r, r.pendingSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableWidgetItem* typeItem = ui_->assocTable->item(row, 0);
|
||||||
|
if (typeItem) {
|
||||||
|
typeItem->setIcon(QIcon(hybridIcon));
|
||||||
|
typeItem->setText(r.dottedExt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column 1: user scope display name.
|
||||||
|
QTableWidgetItem* userItem = ui_->assocTable->item(row, 1);
|
||||||
|
if (userItem) {
|
||||||
|
userItem->setText(r.pendingUser.state == CellState::Blank
|
||||||
|
? QString()
|
||||||
|
: r.pendingUser.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Column 2: system scope display name. When the system column is
|
||||||
|
// inactive (user-only run), the cells are flagged as disabled so that
|
||||||
|
// clicks are ignored and the rendering is greyed out.
|
||||||
|
QTableWidgetItem* systemItem = ui_->assocTable->item(row, 2);
|
||||||
|
if (systemItem) {
|
||||||
|
systemItem->setText(r.pendingSystem.state == CellState::Blank
|
||||||
|
? QString()
|
||||||
|
: r.pendingSystem.name);
|
||||||
|
|
||||||
|
const Qt::ItemFlags enabledFlags =
|
||||||
|
Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||||
|
// Without Qt::ItemIsEnabled the cell renders disabled (greyed out)
|
||||||
|
// and cellClicked is not emitted, so clicks are silently ignored.
|
||||||
|
const Qt::ItemFlags disabledFlags = Qt::ItemIsSelectable;
|
||||||
|
if (isSystemColumnEnabled()) {
|
||||||
|
systemItem->setFlags(enabledFlags);
|
||||||
|
} else {
|
||||||
|
systemItem->setFlags(disabledFlags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::updateApplyButtonEnabled() {
|
||||||
|
bool dirty = false;
|
||||||
|
for (const detail::ExtRow& r : rows_) {
|
||||||
|
if (r.pendingUser.state != r.initialUser.state ||
|
||||||
|
r.pendingSystem.state != r.initialSystem.state) {
|
||||||
|
dirty = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui_->applyButton->setEnabled(dirty);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::toggleCell(int row, int column) {
|
||||||
|
using detail::CellState;
|
||||||
|
|
||||||
|
if (refreshing_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (row < 0 || row >= static_cast<int>(rows_.size())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
detail::CellData* cell = nullptr;
|
||||||
|
const detail::ExtRow* rowPtr = &rows_[row];
|
||||||
|
if (column == 1) {
|
||||||
|
cell = &rows_[row].pendingUser;
|
||||||
|
} else if (column == 2) {
|
||||||
|
if (!isSystemColumnEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cell = &rows_[row].pendingSystem;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle: Self -> Blank, anything else -> Self.
|
||||||
|
if (cell->state == CellState::Self) {
|
||||||
|
cell->state = CellState::Blank;
|
||||||
|
cell->name.clear();
|
||||||
|
cell->icon = QPixmap();
|
||||||
|
} else {
|
||||||
|
cell->state = CellState::Self;
|
||||||
|
cell->name = rowPtr->selfName;
|
||||||
|
cell->icon = rowPtr->selfIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshRowDisplay(row);
|
||||||
|
updateApplyButtonEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::selectAllInScope(bool isUser) {
|
||||||
|
using detail::CellState;
|
||||||
|
|
||||||
|
if (!isUser && !isSystemColumnEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progressively select more. If there is at least one blank cell, the
|
||||||
|
// first click only fills blanks; otherwise the click overrides cells
|
||||||
|
// pointing at other handlers as well.
|
||||||
|
bool hasBlank = false;
|
||||||
|
for (detail::ExtRow& r : rows_) {
|
||||||
|
const detail::CellData& cell = isUser ? r.pendingUser : r.pendingSystem;
|
||||||
|
if (cell.state == CellState::Blank) {
|
||||||
|
hasBlank = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (detail::ExtRow& r : rows_) {
|
||||||
|
detail::CellData& cell = isUser ? r.pendingUser : r.pendingSystem;
|
||||||
|
if (hasBlank) {
|
||||||
|
if (cell.state == CellState::Blank) {
|
||||||
|
cell.state = CellState::Self;
|
||||||
|
cell.name = r.selfName;
|
||||||
|
cell.icon = r.selfIcon;
|
||||||
|
}
|
||||||
|
} else if (cell.state != CellState::Self) {
|
||||||
|
cell.state = CellState::Self;
|
||||||
|
cell.name = r.selfName;
|
||||||
|
cell.icon = r.selfIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < rows_.size(); ++i) {
|
||||||
|
refreshRowDisplay(static_cast<int>(i));
|
||||||
|
}
|
||||||
|
updateApplyButtonEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::applyAllChanges() {
|
||||||
|
// Walk through every row and commit any cell whose pending state differs
|
||||||
|
// from the initial snapshot. wfassoc's link/unlink take an index rather
|
||||||
|
// than a scope/view, so we map columns back to (scope, index) pairs.
|
||||||
|
for (const detail::ExtRow& r : rows_) {
|
||||||
|
if (r.pendingUser.state != r.initialUser.state) {
|
||||||
|
if (r.pendingUser.state == detail::CellState::Self) {
|
||||||
|
program_->LinkExt(wfassocpp::Scope::User, r.index);
|
||||||
|
} else {
|
||||||
|
program_->UnlinkExt(wfassocpp::Scope::User, r.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (r.pendingSystem.state != r.initialSystem.state) {
|
||||||
|
if (r.pendingSystem.state == detail::CellState::Self) {
|
||||||
|
program_->LinkExt(wfassocpp::Scope::System, r.index);
|
||||||
|
} else {
|
||||||
|
program_->UnlinkExt(wfassocpp::Scope::System, r.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-query and rebuild the table so the UI reflects the live registry.
|
||||||
|
rebuildTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssociationWidget::isSystemColumnEnabled() const {
|
||||||
|
return scope_ == TargetScope::System;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::onSelectUserClicked() {
|
||||||
|
selectAllInScope(/*isUser=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::onSelectSystemClicked() {
|
||||||
|
selectAllInScope(/*isUser=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::onCellClicked(int row, int column) {
|
||||||
|
toggleCell(row, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::onOkClicked() {
|
||||||
|
try {
|
||||||
|
applyAllChanges();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
|
// Sync the table with the live registry, since some changes may have
|
||||||
|
// been committed before the failure.
|
||||||
|
refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit changed();
|
||||||
|
emit finished(/*accepted=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::onCancelClicked() {
|
||||||
|
emit finished(/*accepted=*/false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssociationWidget::onApplyClicked() {
|
||||||
|
try {
|
||||||
|
applyAllChanges();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
||||||
|
refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_ASSOCIATION_WIDGET_H_
|
||||||
|
#define QWFASSOC_ASSOCIATION_WIDGET_H_
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "qwfassoc_global.h"
|
||||||
|
#include "scope.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class AssociationWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Internal helper types used by AssociationWidget. They live in a `detail`
|
||||||
|
// namespace to signal that they are not part of the public API even though
|
||||||
|
// they need to be visible in the header.
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
enum class CellState {
|
||||||
|
// The extension has no associated handler in this scope.
|
||||||
|
Blank,
|
||||||
|
// The extension is associated with the application described by the
|
||||||
|
// current manifest.
|
||||||
|
Self,
|
||||||
|
// The extension is associated with some other application.
|
||||||
|
Other,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Per-cell cached data.
|
||||||
|
struct CellData {
|
||||||
|
CellState state = CellState::Blank;
|
||||||
|
// Display name. Only meaningful when state == Other, since the Self name
|
||||||
|
// is shared per row (it does not change between cells).
|
||||||
|
QString name;
|
||||||
|
// Display icon. Used for both Self and Other states.
|
||||||
|
QPixmap icon;
|
||||||
|
};
|
||||||
|
|
||||||
|
// All data attached to a single row in the file association table.
|
||||||
|
struct ExtRow {
|
||||||
|
// wfassoc extension index, used when calling LinkExt/UnlinkExt/QueryExt.
|
||||||
|
size_t index = 0;
|
||||||
|
// Dotted extension body, e.g. ".jpg". Displayed in column 0.
|
||||||
|
QString dottedExt;
|
||||||
|
// Body without leading dot, e.g. "jpg".
|
||||||
|
QString extBody;
|
||||||
|
// The display name when this program is the handler.
|
||||||
|
QString selfName;
|
||||||
|
// The icon when this program is the handler.
|
||||||
|
QPixmap selfIcon;
|
||||||
|
|
||||||
|
// Snapshot taken from the live registry at load/refresh time.
|
||||||
|
CellData initialUser;
|
||||||
|
CellData initialSystem;
|
||||||
|
// Working copy edited by the user; compared against initial* on Apply.
|
||||||
|
CellData pendingUser;
|
||||||
|
CellData pendingSystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// Widget showing the per-extension file-association status of a wfassoc
|
||||||
|
// program, and letting the user stage link / unlink operations.
|
||||||
|
//
|
||||||
|
// The widget follows the two-phase initialization pattern: the constructor
|
||||||
|
// only takes a parent, and the caller invokes setConfig() with the target
|
||||||
|
// program and scope before the widget becomes usable.
|
||||||
|
//
|
||||||
|
// By default the OK and Cancel buttons are hidden because they imply a
|
||||||
|
// dialog-level operation (close). Hosts that embed this widget in a dialog
|
||||||
|
// can enable them through Config::showOkCancelButtons and react to the
|
||||||
|
// finished() signal.
|
||||||
|
class QWFASSOC_EXPORT AssociationWidget : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct Config {
|
||||||
|
// Non-owning pointer to the wfassoc program. Must outlive the widget.
|
||||||
|
wfassocpp::Program* program = nullptr;
|
||||||
|
// Scope that link / unlink operations apply to. Also controls whether
|
||||||
|
// the system column is interactive (only System scope unlocks it).
|
||||||
|
TargetScope scope = TargetScope::User;
|
||||||
|
// Whether the OK and Cancel buttons are visible. They are hidden by
|
||||||
|
// default since closing the host window is a host-level decision.
|
||||||
|
bool showOkCancelButtons = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit AssociationWidget(QWidget* parent = nullptr);
|
||||||
|
~AssociationWidget() override;
|
||||||
|
|
||||||
|
// Two-phase initialization.
|
||||||
|
void setConfig(const Config& config);
|
||||||
|
|
||||||
|
// Re-query the live wfassoc state and rebuild the table.
|
||||||
|
void refresh();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Emitted after the user applies pending changes (OK or Apply). The host
|
||||||
|
// should refresh every widget that depends on wfassoc state in response.
|
||||||
|
void changed();
|
||||||
|
// Emitted when the widget wants its host window to close. `accepted` is
|
||||||
|
// true when the OK button was used (after the changes were applied and
|
||||||
|
// changed() was emitted) and false when the Cancel button was used.
|
||||||
|
void finished(bool accepted);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onSelectUserClicked();
|
||||||
|
void onSelectSystemClicked();
|
||||||
|
void onCellClicked(int row, int column);
|
||||||
|
void onOkClicked();
|
||||||
|
void onCancelClicked();
|
||||||
|
void onApplyClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Refresh install / apply button enable state based on the live registry
|
||||||
|
// and the pending edits.
|
||||||
|
void updateEnabledState();
|
||||||
|
// Drop and rebuild the table contents from the live registry.
|
||||||
|
void rebuildTable();
|
||||||
|
// Refresh a single row's displayed cells from its pending state.
|
||||||
|
void refreshRowDisplay(int row);
|
||||||
|
// Update the "Apply" button enable state based on whether any pending
|
||||||
|
// cell differs from its initial state.
|
||||||
|
void updateApplyButtonEnabled();
|
||||||
|
// Toggle the pending state of a (row, column) cell. Column 1 maps to the
|
||||||
|
// user scope, column 2 to the system scope.
|
||||||
|
void toggleCell(int row, int column);
|
||||||
|
// Apply the "+" semantics to a given scope: blank cells become Self, and
|
||||||
|
// if there are none, all non-Self cells become Self.
|
||||||
|
void selectAllInScope(bool isUser);
|
||||||
|
// Commit every pending change to the registry via wfassoc.
|
||||||
|
void applyAllChanges();
|
||||||
|
// True when the system column should be interactive.
|
||||||
|
bool isSystemColumnEnabled() const;
|
||||||
|
|
||||||
|
Ui::AssociationWidget* ui_;
|
||||||
|
wfassocpp::Program* program_ = nullptr;
|
||||||
|
TargetScope scope_ = TargetScope::User;
|
||||||
|
bool showOkCancelButtons_ = false;
|
||||||
|
|
||||||
|
QString programName_;
|
||||||
|
QString userName_;
|
||||||
|
|
||||||
|
std::vector<detail::ExtRow> rows_;
|
||||||
|
// Re-entrancy guard used while rebuilding the table to avoid feeding
|
||||||
|
// model-change signals back into toggleCell().
|
||||||
|
bool refreshing_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_ASSOCIATION_WIDGET_H_
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>AssociationWidget</class>
|
||||||
|
<widget class="QWidget" name="AssociationWidget">
|
||||||
|
<layout class="QVBoxLayout" name="mainLayout">
|
||||||
|
<property name="leftMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="rightMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="bottomMargin">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="assocHeaderLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string>File types associated with this application:</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="selectButtonsLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="selectUserButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Select all for current user</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">+</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="selectSystemButton">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Select all for all users</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string notr="true">+</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="selectButtonsSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QTableWidget" name="assocTable">
|
||||||
|
<property name="editTriggers">
|
||||||
|
<set>QAbstractItemView::NoEditTriggers</set>
|
||||||
|
</property>
|
||||||
|
<property name="selectionMode">
|
||||||
|
<enum>QAbstractItemView::SingleSelection</enum>
|
||||||
|
</property>
|
||||||
|
<property name="selectionBehavior">
|
||||||
|
<enum>QAbstractItemView::SelectItems</enum>
|
||||||
|
</property>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>Type</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>User</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
<column>
|
||||||
|
<property name="text">
|
||||||
|
<string>All Users</string>
|
||||||
|
</property>
|
||||||
|
</column>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="bottomButtonsLayout">
|
||||||
|
<item>
|
||||||
|
<spacer name="bottomButtonsSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="okButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>OK</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="default">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="cancelButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Cancel</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="visible">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="applyButton">
|
||||||
|
<property name="text">
|
||||||
|
<string>Apply</string>
|
||||||
|
</property>
|
||||||
|
<property name="autoDefault">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "icon_utils.h"
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
|
||||||
|
// QImage::fromHICON is only available on Windows and requires the real Win32
|
||||||
|
// HICON typedef to be visible. The qt_windows.h wrapper (shipped with Qt 6 on
|
||||||
|
// Windows) takes care of including <windows.h> in the order Qt expects, so it
|
||||||
|
// is included after all Qt headers.
|
||||||
|
#include <qt_windows.h>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
namespace icon_utils {
|
||||||
|
|
||||||
|
QPixmap fromHicon(wfassocpp::HICON handle) {
|
||||||
|
if (handle == wfassocpp::INVALID_HICON) {
|
||||||
|
return QPixmap();
|
||||||
|
}
|
||||||
|
QImage image = QImage::fromHICON(static_cast<HICON>(handle));
|
||||||
|
return QPixmap::fromImage(std::move(image));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace icon_utils
|
||||||
|
} // namespace qwfassoc
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_ICON_UTILS_H_
|
||||||
|
#define QWFASSOC_ICON_UTILS_H_
|
||||||
|
|
||||||
|
#include <QPixmap>
|
||||||
|
|
||||||
|
#include <wfassoc++.h>
|
||||||
|
|
||||||
|
#include "qwfassoc_global.h"
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
namespace icon_utils {
|
||||||
|
|
||||||
|
// Convert a wfassocpp::HICON handle (the C++ wrapper around wfassoc's opaque
|
||||||
|
// icon handle) into a QPixmap suitable for use in Qt widgets.
|
||||||
|
//
|
||||||
|
// The conversion goes through QImage::fromHICON() (available since Qt 6.0 on
|
||||||
|
// Windows) and then QPixmap::fromImage() using its rvalue-reference overload
|
||||||
|
// so that no extra pixel buffer copy is performed. The returned pixmap is null
|
||||||
|
// if the input handle is null.
|
||||||
|
QWFASSOC_EXPORT QPixmap fromHicon(wfassocpp::HICON handle);
|
||||||
|
|
||||||
|
} // namespace icon_utils
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_ICON_UTILS_H_
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_MANIFEST_H_
|
||||||
|
#define QWFASSOC_MANIFEST_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// Description of a single extension declared in the manifest.
|
||||||
|
//
|
||||||
|
// For a given extension (e.g. "jpg"), the manifest references one entry from
|
||||||
|
// each of the `strs`, `icons` and `behaviors` tables by its token name.
|
||||||
|
struct ManifestExt {
|
||||||
|
std::string name;
|
||||||
|
std::string icon;
|
||||||
|
std::string behavior;
|
||||||
|
};
|
||||||
|
|
||||||
|
// In-memory representation of a wfassoc manifest TOML file.
|
||||||
|
//
|
||||||
|
// This struct mirrors the Rust `Manifest` type defined in
|
||||||
|
// `wfassoc-exec/src/manifest.rs`. It is a plain data struct: parsing from
|
||||||
|
// TOML and conversion into a wfassocpp::Schema are intentionally kept out of
|
||||||
|
// the library so that the library itself does not depend on a TOML parser.
|
||||||
|
// Consumers (such as qwfassoc-standalone) are responsible for filling the
|
||||||
|
// fields in.
|
||||||
|
struct Manifest {
|
||||||
|
std::string identifier;
|
||||||
|
std::string path;
|
||||||
|
std::string clsid;
|
||||||
|
|
||||||
|
std::optional<std::string> name;
|
||||||
|
std::optional<std::string> icon;
|
||||||
|
std::optional<std::string> behavior;
|
||||||
|
|
||||||
|
std::map<std::string, std::string> strs;
|
||||||
|
std::map<std::string, std::string> icons;
|
||||||
|
std::map<std::string, std::string> behaviors;
|
||||||
|
std::map<std::string, ManifestExt> exts;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_MANIFEST_H_
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_GLOBAL_H_
|
||||||
|
#define QWFASSOC_GLOBAL_H_
|
||||||
|
|
||||||
|
#include <qglobal.h>
|
||||||
|
|
||||||
|
// Standard Qt shared-library export/import macros.
|
||||||
|
//
|
||||||
|
// When the qwfassoc library itself is being built, QWFASSOC_LIBRARY is
|
||||||
|
// defined (see the library's CMakeLists.txt) and QWFASSOC_EXPORT expands to
|
||||||
|
// Q_DECL_EXPORT so that symbols are exported from the .dll. Consumers of the
|
||||||
|
// library leave QWFASSOC_LIBRARY undefined, so QWFASSOC_EXPORT expands to
|
||||||
|
// Q_DECL_IMPORT and the same symbols are imported.
|
||||||
|
#if defined(QWFASSOC_LIBRARY)
|
||||||
|
# define QWFASSOC_EXPORT Q_DECL_EXPORT
|
||||||
|
#else
|
||||||
|
# define QWFASSOC_EXPORT Q_DECL_IMPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // QWFASSOC_GLOBAL_H_
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifndef QWFASSOC_SCOPE_H_
|
||||||
|
#define QWFASSOC_SCOPE_H_
|
||||||
|
|
||||||
|
namespace qwfassoc {
|
||||||
|
|
||||||
|
// The target scope an application is being managed for.
|
||||||
|
//
|
||||||
|
// This value typically comes from a `--for user` / `--for system` command
|
||||||
|
// line argument and decides which columns of the file-association table are
|
||||||
|
// interactive, as well as which scope install / unregister operations apply
|
||||||
|
// to.
|
||||||
|
enum class TargetScope {
|
||||||
|
User,
|
||||||
|
System,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qwfassoc
|
||||||
|
|
||||||
|
#endif // QWFASSOC_SCOPE_H_
|
||||||
@@ -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>
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Wfassoc C/C++ Library
|
# WFAssoc C/C++ Binding
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -0,0 +1,693 @@
|
|||||||
|
/**
|
||||||
|
* @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>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Windows File Association C++ API namespace
|
||||||
|
*
|
||||||
|
* Provides C++ RAII wrappers around the C-compatible wfassoc API.
|
||||||
|
*/
|
||||||
|
namespace wfassocpp {
|
||||||
|
|
||||||
|
/** @brief Type representing a null-terminated UTF-8 C-style string */
|
||||||
|
using CString = wfassoc::WFCString;
|
||||||
|
/**
|
||||||
|
* @brief Type representing a handle/token for managed objects
|
||||||
|
*
|
||||||
|
* This library uses an object pool to manage any objects created during calling.
|
||||||
|
* This type is exposed as an opaque handle for visiting your created object.
|
||||||
|
*/
|
||||||
|
using Token = wfassoc::WFToken;
|
||||||
|
/**
|
||||||
|
* @brief Type representing an icon handle (opaque pointer)
|
||||||
|
*
|
||||||
|
* This type is equivalent to the Win32 HICON type.
|
||||||
|
*/
|
||||||
|
using HICON = wfassoc::WFHICON;
|
||||||
|
/** @brief Invalid icon handle value */
|
||||||
|
using INVALID_HICON = wfassoc::WF_INVALID_HICON;
|
||||||
|
/** @brief Invalid index value used for error conditions */
|
||||||
|
using INVALID_INDEX = wfassoc::WF_INVALID_INDEX;
|
||||||
|
/**
|
||||||
|
* @brief Registration scope for file associations
|
||||||
|
*
|
||||||
|
* Determines whether a program is registered for the current user or system-wide.
|
||||||
|
*/
|
||||||
|
using Scope = wfassoc::WFScope;
|
||||||
|
/**
|
||||||
|
* @brief View mode for querying file association status
|
||||||
|
*
|
||||||
|
* Determines how the association status is viewed/queried.
|
||||||
|
*/
|
||||||
|
using View = wfassoc::WFView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @brief Check the result of a C API call and throw on failure
|
||||||
|
*
|
||||||
|
* @param[in] result Boolean result from a C API call
|
||||||
|
* @throws std::runtime_error if result is false, with the error message from WFGetLastError()
|
||||||
|
*/
|
||||||
|
inline void _Check(bool result) {
|
||||||
|
if (!result) {
|
||||||
|
throw std::runtime_error(wfassoc::WFGetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @brief Get the invalid token value
|
||||||
|
*
|
||||||
|
* In theory, invalid token value should be a constant value.
|
||||||
|
* However, due to the library used in Rust side, this value can only be fetched at runtime.
|
||||||
|
* This function caches the value on first call for subsequent use.
|
||||||
|
*
|
||||||
|
* @return An invalid token value
|
||||||
|
*/
|
||||||
|
inline Token _INVALID_TOKEN() {
|
||||||
|
static Token v = wfassoc::WFInvalidToken();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Schema object for defining a program's file association configuration
|
||||||
|
*
|
||||||
|
* A Schema is a sketchpad of a complete program.
|
||||||
|
* Create a Schema object first, then convert it to a Program object for following operations.
|
||||||
|
*
|
||||||
|
* This class is move-only (non-copyable).
|
||||||
|
*/
|
||||||
|
class Schema {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Schema object
|
||||||
|
* @throws std::runtime_error if schema creation fails
|
||||||
|
*/
|
||||||
|
Schema() { _Check(wfassoc::WFSchemaCreate(&_token)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Schema object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the Schema object.
|
||||||
|
*/
|
||||||
|
~Schema() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSchemaDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
Schema(const Schema&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
Schema& operator=(const Schema&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
|
Schema(Schema&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
|
Schema& operator=(Schema&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSchemaDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program identifier for this Schema
|
||||||
|
*
|
||||||
|
* @param[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.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void SetIdentifier(const char* value) { _Check(wfassoc::WFSchemaSetIdentifier(_token, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program path for this Schema
|
||||||
|
*
|
||||||
|
* @param[in] value Null-terminated UTF-8 string containing the program path.
|
||||||
|
* This path should be the fully qualified path to the application.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void SetPath(const char* value) { _Check(wfassoc::WFSchemaSetPath(_token, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program CLSID for this Schema
|
||||||
|
*
|
||||||
|
* @param[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.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void SetClsid(const char* value) { _Check(wfassoc::WFSchemaSetClsid(_token, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program name for this Schema (optional)
|
||||||
|
*
|
||||||
|
* @param[in] value Null-terminated UTF-8 string containing the name, or nullptr to clear
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void SetName(const char* value) { _Check(wfassoc::WFSchemaSetName(_token, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program icon for this Schema (optional)
|
||||||
|
*
|
||||||
|
* @param[in] value Null-terminated UTF-8 string containing the icon path, or nullptr to clear
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void SetIcon(const char* value) { _Check(wfassoc::WFSchemaSetIcon(_token, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the program behavior for this Schema (optional)
|
||||||
|
*
|
||||||
|
* @param[in] value Null-terminated UTF-8 string containing the behavior command, or nullptr to clear
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void SetBehavior(const char* value) { _Check(wfassoc::WFSchemaSetBehavior(_token, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a string resource entry to this Schema
|
||||||
|
*
|
||||||
|
* @param[in] name Null-terminated UTF-8 string containing the name of this entry
|
||||||
|
* @param[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.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void AddStr(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddStr(_token, name, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add an icon registry entry to this Schema
|
||||||
|
*
|
||||||
|
* @param[in] name Null-terminated UTF-8 string containing the name of this entry
|
||||||
|
* @param[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.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void AddIcon(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddIcon(_token, name, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a behavior registry entry to this Schema
|
||||||
|
*
|
||||||
|
* @param[in] name Null-terminated UTF-8 string containing the name of this entry
|
||||||
|
* @param[in] value Null-terminated UTF-8 string containing the value of this entry.
|
||||||
|
* It should be a valid command line string which uses @c %1, @c %2, etc. to represent parameters.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void AddBehavior(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddBehavior(_token, name, value)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add a file extension to this Schema
|
||||||
|
*
|
||||||
|
* @param[in] ext Null-terminated UTF-8 string containing the file extension name (without leading dot)
|
||||||
|
* @param[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 AddStr().
|
||||||
|
* @param[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 AddIcon().
|
||||||
|
* @param[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 AddBehavior().
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
* @brief Release ownership of the token
|
||||||
|
*
|
||||||
|
* @return The released token
|
||||||
|
*/
|
||||||
|
Token Release() noexcept {
|
||||||
|
Token t = _token;
|
||||||
|
_token = _INVALID_TOKEN();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Icon resource object for managing icon handles
|
||||||
|
*
|
||||||
|
* This class wraps an icon resource token and provides access to the icon handle.
|
||||||
|
* The icon handle will be freed when this object is destroyed.
|
||||||
|
*
|
||||||
|
* This class is move-only (non-copyable).
|
||||||
|
*/
|
||||||
|
class IconRc {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct an IconRc from an existing token
|
||||||
|
*
|
||||||
|
* @param[in] token The icon resource token to wrap
|
||||||
|
*/
|
||||||
|
explicit IconRc(Token token) : _token(token) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the IconRc object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the icon resource.
|
||||||
|
*/
|
||||||
|
~IconRc() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFIconRcDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
IconRc(const IconRc&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
IconRc& operator=(const IconRc&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
|
IconRc(IconRc&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
|
IconRc& operator=(IconRc&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFIconRcDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the icon handle from this icon resource
|
||||||
|
*
|
||||||
|
* @return The icon handle. This handle will be freed when this object is destroyed.
|
||||||
|
* Please make a copy immediately if you need to use it longer.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
HICON GetIcon() {
|
||||||
|
HICON icon = nullptr;
|
||||||
|
_Check(wfassoc::WFIconRcGetIcon(_token, &icon));
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Extension status object for querying file extension registration status
|
||||||
|
*
|
||||||
|
* This class wraps an extension status token and provides access to extension details.
|
||||||
|
* The extension status object will be freed when this object is destroyed.
|
||||||
|
*
|
||||||
|
* This class is move-only (non-copyable).
|
||||||
|
*/
|
||||||
|
class ExtStatus {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct an ExtStatus from an existing token
|
||||||
|
*
|
||||||
|
* @param[in] token The extension status token to wrap
|
||||||
|
*/
|
||||||
|
explicit ExtStatus(Token token) : _token(token) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the ExtStatus object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the extension status.
|
||||||
|
*/
|
||||||
|
~ExtStatus() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
ExtStatus(const ExtStatus&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
ExtStatus& operator=(const ExtStatus&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
|
ExtStatus(ExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
|
ExtStatus& operator=(ExtStatus&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the display name from this extension status
|
||||||
|
*
|
||||||
|
* The display name will be user specified first, then fallback to its ProgId verbatim.
|
||||||
|
* The library will try to use localized name first, then use raw ProgId name if localized name is not available.
|
||||||
|
*
|
||||||
|
* @return The display name string. There is no possibility that this value is empty.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
std::string GetName() {
|
||||||
|
const char* name = nullptr;
|
||||||
|
_Check(wfassoc::WFExtStatusGetName(_token, &name));
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the icon from this extension status
|
||||||
|
*
|
||||||
|
* The icon will be user specified first, then fallback to the system default file icon.
|
||||||
|
*
|
||||||
|
* @return The icon handle. This handle will be freed when this object is destroyed.
|
||||||
|
* Please make a copy immediately if you need to use it longer.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
HICON GetIcon() {
|
||||||
|
HICON icon = nullptr;
|
||||||
|
_Check(wfassoc::WFExtStatusGetIcon(_token, &icon));
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Self extension status object for accessing program-provided extension details
|
||||||
|
*
|
||||||
|
* This class wraps a self extension status token and provides access to extension details
|
||||||
|
* as defined by the program itself.
|
||||||
|
*
|
||||||
|
* This class is move-only (non-copyable).
|
||||||
|
*/
|
||||||
|
class SelfExtStatus {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a SelfExtStatus from an existing token
|
||||||
|
*
|
||||||
|
* @param[in] token The self extension status token to wrap
|
||||||
|
*/
|
||||||
|
explicit SelfExtStatus(Token token) : _token(token) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the SelfExtStatus object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the self extension status.
|
||||||
|
*/
|
||||||
|
~SelfExtStatus() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSelfExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
SelfExtStatus(const SelfExtStatus&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
SelfExtStatus& operator=(const SelfExtStatus&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
|
SelfExtStatus(SelfExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
|
SelfExtStatus& operator=(SelfExtStatus&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFSelfExtStatusDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the display name from this self extension status
|
||||||
|
*
|
||||||
|
* The display name will be user specified first, then fallback to its ProgId verbatim.
|
||||||
|
*
|
||||||
|
* @return The display name string. There is no possibility that this value is empty.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
std::string GetName() {
|
||||||
|
const char* name = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetName(_token, &name));
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the icon from this self extension status
|
||||||
|
*
|
||||||
|
* The icon will be user specified first, then fallback to the system default file icon.
|
||||||
|
*
|
||||||
|
* @return The icon handle. This handle will be freed when this object is destroyed.
|
||||||
|
* Please make a copy immediately if you need to use it longer.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
HICON GetIcon() {
|
||||||
|
HICON icon = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetIcon(_token, &icon));
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the extension string (without leading dot) from this self extension status
|
||||||
|
*
|
||||||
|
* @return The file extension name (without leading dot). There is no possibility that this value is empty.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
std::string GetExt() {
|
||||||
|
const char* inner = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetExt(_token, &inner));
|
||||||
|
return std::string(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the dotted extension string (with leading dot) from this self extension status
|
||||||
|
*
|
||||||
|
* @return The file extension string (with leading dot). There is no possibility that this value is empty.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
std::string GetDottedExt() {
|
||||||
|
const char* inner = nullptr;
|
||||||
|
_Check(wfassoc::WFSelfExtStatusGetDottedExt(_token, &inner));
|
||||||
|
return std::string(inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Token _token;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Program object for managing file association registration
|
||||||
|
*
|
||||||
|
* A Program is created from a Schema and provides methods for registering,
|
||||||
|
* unregistering, and querying file associations.
|
||||||
|
*
|
||||||
|
* This class is move-only (non-copyable).
|
||||||
|
*/
|
||||||
|
class Program {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a Program from a Schema
|
||||||
|
*
|
||||||
|
* This constructor consumes the Schema object.
|
||||||
|
* The Schema object cannot be used after this call.
|
||||||
|
*
|
||||||
|
* @param[in] schema The Schema to create the Program from (will be consumed)
|
||||||
|
* @throws std::runtime_error if program creation fails
|
||||||
|
*/
|
||||||
|
explicit Program(Schema&& schema) {
|
||||||
|
_Check(wfassoc::WFProgramCreate(schema.Release(), &_token));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Program object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the Program.
|
||||||
|
*/
|
||||||
|
~Program() {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFProgramDestroy(_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
Program(const Program&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
|
Program& operator=(const Program&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
|
Program(Program&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
|
Program& operator=(Program&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (_token != _INVALID_TOKEN()) {
|
||||||
|
wfassoc::WFProgramDestroy(_token);
|
||||||
|
}
|
||||||
|
_token = other._token;
|
||||||
|
other._token = _INVALID_TOKEN();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*
|
||||||
|
* @return The resolved program name. There is no possibility that this value is empty.
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
std::string ResolveName() {
|
||||||
|
const char* name = nullptr;
|
||||||
|
_Check(wfassoc::WFProgramResolveName(_token, &name));
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resolve the Program icon resource
|
||||||
|
*
|
||||||
|
* The icon will be user specified first,
|
||||||
|
* then fallback to the first icon of program,
|
||||||
|
* and finally fallback to the system default executable icon.
|
||||||
|
*
|
||||||
|
* @return An IconRc object containing the resolved icon
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
IconRc ResolveIcon() {
|
||||||
|
Token token = _INVALID_TOKEN();
|
||||||
|
_Check(wfassoc::WFProgramResolveIcon(_token, &token));
|
||||||
|
return IconRc(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of file extensions in the Program
|
||||||
|
*
|
||||||
|
* @return The number of extensions
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
size_t ExtsLen() {
|
||||||
|
size_t len = 0;
|
||||||
|
_Check(wfassoc::WFProgramExtsLen(_token, &len));
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find a file extension by its body (extension string)
|
||||||
|
*
|
||||||
|
* @param[in] body Null-terminated UTF-8 string containing the file extension name (without leading dot) to find
|
||||||
|
* @return The index of the extension, or INVALID_INDEX if not found
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
size_t FindExt(const char* body) {
|
||||||
|
size_t index = INVALID_INDEX;
|
||||||
|
_Check(wfassoc::WFProgramFindExt(_token, body, &index));
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Resolve this program provided extension's details by index
|
||||||
|
*
|
||||||
|
* @param[in] index Index of the extension to resolve
|
||||||
|
* @return A SelfExtStatus object containing the extension details
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
SelfExtStatus ResolveExt(size_t index) {
|
||||||
|
Token token = _INVALID_TOKEN();
|
||||||
|
_Check(wfassoc::WFProgramResolveExt(_token, index, &token));
|
||||||
|
return SelfExtStatus(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register the Program in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] scope Registration scope
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void Register(Scope scope) { _Check(wfassoc::WFProgramRegister(_token, scope)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unregister the Program from the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] scope Registration scope
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void Unregister(Scope scope) { _Check(wfassoc::WFProgramUnregister(_token, scope)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the Program is registered in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] scope Registration scope for checking
|
||||||
|
* @return true if the Program is registered in the specified scope, false otherwise
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
bool IsRegistered(Scope scope) {
|
||||||
|
bool result = false;
|
||||||
|
_Check(wfassoc::WFProgramIsRegistered(_token, scope, &result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Link a file extension in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] scope Registration scope
|
||||||
|
* @param[in] index Index of the extension to link
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void LinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramLinkExt(_token, scope, index)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unlink a file extension in the specified scope
|
||||||
|
*
|
||||||
|
* @param[in] scope Registration scope
|
||||||
|
* @param[in] index Index of the extension to unlink
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
void UnlinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramUnlinkExt(_token, scope, index)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Query the status of a file extension
|
||||||
|
*
|
||||||
|
* @param[in] view View viewpoint
|
||||||
|
* @param[in] index Index of the extension to query
|
||||||
|
* @return An optional ExtStatus object. Returns std::nullopt if the extension is not found
|
||||||
|
* (usually means that this extension is not registered in the specified scope).
|
||||||
|
* @throws std::runtime_error if the operation fails
|
||||||
|
*/
|
||||||
|
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_
|
||||||
@@ -0,0 +1,595 @@
|
|||||||
|
/**
|
||||||
|
* @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
|
||||||
|
/**
|
||||||
|
* @brief Windows File Association C/C++ API namespace
|
||||||
|
*
|
||||||
|
* This namespace only provide the raw binding to C-compatible wfassoc API in C++.
|
||||||
|
* For the C++ RAII wrappers of this, please see @ref wfassocpp namespace.
|
||||||
|
*/
|
||||||
|
namespace wfassoc {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
/** Type representing a null-terminated UTF-8 C-style string */
|
||||||
|
using WFCString = 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 WFToken = uint64_t;
|
||||||
|
/**
|
||||||
|
* @brief Type representing an icon handle (opaque pointer)
|
||||||
|
*
|
||||||
|
* This type is equivalent with Win32 HICON type.
|
||||||
|
*/
|
||||||
|
using WFHICON = void*;
|
||||||
|
#else // __cplusplus
|
||||||
|
typedef const char *WFCString;
|
||||||
|
typedef uint64_t WFToken;
|
||||||
|
typedef void *WFHICON;
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
/** Invalid icon handle value */
|
||||||
|
constexpr WFHICON WF_INVALID_HICON = nullptr;
|
||||||
|
/** Invalid index value used for error conditions */
|
||||||
|
constexpr size_t WF_INVALID_INDEX = static_cast<size_t>(-1);
|
||||||
|
#else // __cplusplus
|
||||||
|
static const WFHICON WF_INVALID_HICON = NULL;
|
||||||
|
static const size_t WF_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 WFScope : 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 WFView : 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 WFScope;
|
||||||
|
/** Current user scope */
|
||||||
|
static const WFScope WF_SCOPE_USER = 0u;
|
||||||
|
/** System-wide scope */
|
||||||
|
static const WFScope WF_SCOPE_SYSTEM = 1u;
|
||||||
|
typedef uint32_t WFView;
|
||||||
|
/** User-level view */
|
||||||
|
static const WFView WF_VIEW_USER = 0u;
|
||||||
|
/** System-level view */
|
||||||
|
static const WFView WF_VIEW_SYSTEM = 1u;
|
||||||
|
/** Combined hybrid view of both user and system */
|
||||||
|
static const WFView WF_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.
|
||||||
|
*/
|
||||||
|
WFCString 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
|
||||||
|
*/
|
||||||
|
WFToken WFInvalidWFToken(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(WFToken *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(WFToken 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(WFToken in_schema, WFCString 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(WFToken in_schema, WFCString 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(WFToken in_schema, WFCString 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(WFToken in_schema, WFCString 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(WFToken in_schema, WFCString 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(WFToken in_schema, WFCString 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(WFToken in_schema, WFCString in_name, WFCString 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(WFToken in_schema, WFCString in_name, WFCString 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(WFToken in_schema, WFCString in_name, WFCString 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(WFToken in_schema,
|
||||||
|
WFCString in_ext,
|
||||||
|
WFCString in_ext_name,
|
||||||
|
WFCString in_ext_icon,
|
||||||
|
WFCString 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(WFToken in_schema, WFToken *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(WFToken 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(WFToken in_program, WFCString *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(WFToken in_program, WFToken *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(WFToken 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 WF_INVALID_INDEX if not found.
|
||||||
|
* @return true on success, false on failure
|
||||||
|
*/
|
||||||
|
bool WFProgramFindExt(WFToken in_program, WFCString 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(WFToken in_program, size_t in_index, WFToken *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(WFToken in_program, WFScope 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(WFToken in_program, WFScope 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(WFToken in_program, WFScope 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(WFToken in_program, WFScope 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(WFToken in_program, WFScope 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(WFToken in_program, WFView in_view, size_t in_index, WFToken *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(WFToken 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(WFToken in_ext_status, WFCString *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(WFToken in_ext_status, WFHICON *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(WFToken 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(WFToken in_self_ext_status, WFCString *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(WFToken in_self_ext_status, WFHICON *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(WFToken in_self_ext_status, WFCString *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(WFToken in_self_ext_status, WFCString *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(WFToken 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 WF_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(WFToken in_icon_rc, WFHICON *out_icon);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // namespace wfassoc
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif // WFASSOC_H_
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
#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
|
|
||||||
using CStyleString = const char*;
|
|
||||||
using Token = uint64_t;
|
|
||||||
using HICON = void*;
|
|
||||||
#else // __cplusplus
|
|
||||||
typedef const char *CStyleString;
|
|
||||||
typedef uint64_t Token;
|
|
||||||
typedef void *HICON;
|
|
||||||
#endif // __cplusplus
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
enum class Scope : uint32_t {
|
|
||||||
User = 0u,
|
|
||||||
System = 1u
|
|
||||||
};
|
|
||||||
enum class View : uint32_t {
|
|
||||||
User = 0u,
|
|
||||||
System = 1u,
|
|
||||||
Hybrid = 2u
|
|
||||||
};
|
|
||||||
#else // __cplusplus
|
|
||||||
typedef uint32_t Scope;
|
|
||||||
static const Scope SCOPE_USER = 0u;
|
|
||||||
static const Scope SCOPE_SYSTEM = 1u;
|
|
||||||
typedef uint32_t View;
|
|
||||||
static const View VIEW_USER = 0u;
|
|
||||||
static const View VIEW_SYSTEM = 1u;
|
|
||||||
static const View VIEW_HYBRID = 2u;
|
|
||||||
#endif // __cplusplus
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
namespace wfassoc {
|
|
||||||
extern "C" {
|
|
||||||
#endif // __cplusplus
|
|
||||||
|
|
||||||
bool WFStartup(void);
|
|
||||||
|
|
||||||
bool WFShutdown(void);
|
|
||||||
|
|
||||||
CStyleString WFGetLastError(void);
|
|
||||||
|
|
||||||
bool WFHasPrivilege(void);
|
|
||||||
|
|
||||||
Token WFInvalidToken(void);
|
|
||||||
|
|
||||||
bool WFSchemaCreate(Token *out_schema);
|
|
||||||
|
|
||||||
bool WFSchemaDestroy(Token in_schema);
|
|
||||||
|
|
||||||
bool WFSchemaSetIdentifier(Token in_schema, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaSetPath(Token in_schema, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaSetClsid(Token in_schema, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaSetName(Token in_schema, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaSetIcon(Token in_schema, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaSetBehavior(Token in_schema, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaAddStr(Token in_schema, CStyleString in_name, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaAddIcon(Token in_schema, CStyleString in_name, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaAddBehavior(Token in_schema, CStyleString in_name, CStyleString in_value);
|
|
||||||
|
|
||||||
bool WFSchemaAddExt(Token in_schema,
|
|
||||||
CStyleString in_ext,
|
|
||||||
CStyleString in_ext_name,
|
|
||||||
CStyleString in_ext_icon,
|
|
||||||
CStyleString in_ext_behavior);
|
|
||||||
|
|
||||||
bool WFProgramCreate(Token in_schema, Token *out_program);
|
|
||||||
|
|
||||||
bool WFProgramDestroy(Token in_program);
|
|
||||||
|
|
||||||
bool WFProgramRegister(Token in_program, Scope in_scope);
|
|
||||||
|
|
||||||
bool WFProgramUnregister(Token in_program, Scope in_scope);
|
|
||||||
|
|
||||||
bool WFProgramIsRegistered(Token in_program, Scope in_scope, bool *out_is_registered);
|
|
||||||
|
|
||||||
bool WFProgramLinkExt(Token in_program, Scope in_scope, size_t in_index);
|
|
||||||
|
|
||||||
bool WFProgramUnlinkExt(Token in_program, Scope in_scope, size_t in_index);
|
|
||||||
|
|
||||||
bool WFProgramQueryExt(Token in_program, View in_view, size_t in_index, Token *out_ext_status);
|
|
||||||
|
|
||||||
bool WFExtStatusDestroy(Token in_ext_status);
|
|
||||||
|
|
||||||
bool WFExtStatusGetName(Token in_ext_status, CStyleString *out_name);
|
|
||||||
|
|
||||||
bool WFExtStatusGetIcon(Token in_ext_status, HICON *out_icon);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} // extern "C"
|
|
||||||
} // namespace wfassoc
|
|
||||||
#endif // __cplusplus
|
|
||||||
|
|
||||||
#endif // __WFASSOC_H__
|
|
||||||
@@ -9,11 +9,9 @@
|
|||||||
//! The only thing that outer programs should note is that this string is volatile,
|
//! 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.
|
//! once they get it, they must dupliate it immediately before any futher calling to this dynamic library.
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::ffi::{CStr, CString, c_char};
|
use std::ffi::{CStr, CString};
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
|
use crate::ffi_types::CStyleString;
|
||||||
/// The type representing the raw pointer to immutable C-style NUL-terminated string.
|
|
||||||
pub type CStyleString = *const c_char;
|
|
||||||
|
|
||||||
// region: Error
|
// region: Error
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
//! The module including all FFI types used by this crate, except string type.
|
//! The module including all FFI types used by this crate, except string type.
|
||||||
//! For string type, see also [crate::cstr_ffi].
|
//! For string type, see also [crate::cstr_ffi].
|
||||||
|
|
||||||
use std::ffi::c_void;
|
use std::ffi::{c_void, c_char};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
// region: File Extension Index
|
// region: Misc Types and Constants
|
||||||
|
|
||||||
|
/// The type representing the raw pointer to immutable C-style NUL-terminated string.
|
||||||
|
pub type CStyleString = *const c_char;
|
||||||
|
|
||||||
|
/// The invalid value of index.
|
||||||
pub const INVALID_INDEX: usize = usize::MAX;
|
pub const INVALID_INDEX: usize = usize::MAX;
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@@ -18,12 +22,12 @@ pub const INVALID_INDEX: usize = usize::MAX;
|
|||||||
/// However, I don't want to add it as this crate's dependency,
|
/// However, I don't want to add it as this crate's dependency,
|
||||||
/// because I don't use anything within it except this type.
|
/// because I don't use anything within it except this type.
|
||||||
/// So I check Microsoft document, re-define it in there for this crate.
|
/// 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
|
/// Reference: <https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types>
|
||||||
pub type HICON = *mut c_void;
|
pub type HICON = *mut c_void;
|
||||||
|
|
||||||
/// The invalid value of Win32 HICON handle.
|
/// The invalid value of Win32 HICON handle.
|
||||||
///
|
///
|
||||||
/// The same reason like [HICON] to re-define it in there.
|
/// The same reason like [WFHICON] to re-define it in there.
|
||||||
pub const INVALID_HICON: HICON = std::ptr::null_mut();
|
pub const INVALID_HICON: HICON = std::ptr::null_mut();
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
+208
-193
@@ -14,27 +14,27 @@ use wfassoc::highlevel::{Program, Schema};
|
|||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
enum Error {
|
enum Error {
|
||||||
/// Error when operating Schema.
|
/// Error when operating Schema.
|
||||||
#[error("{0}")]
|
#[error("wfassoc error: {0}")]
|
||||||
Schema(#[from] wfassoc::highlevel::SchemaError),
|
Schema(#[from] wfassoc::highlevel::SchemaError),
|
||||||
/// Error when parsing Schema into Program.
|
/// Error when parsing Schema into Program.
|
||||||
#[error("{0}")]
|
#[error("wfassoc error: {0}")]
|
||||||
ParseProgram(#[from] wfassoc::highlevel::ParseProgramError),
|
ParseProgram(#[from] wfassoc::highlevel::ParseProgramError),
|
||||||
/// Error when operating Program.
|
/// Error when operating Program.
|
||||||
#[error("{0}")]
|
#[error("wfassoc error: {0}")]
|
||||||
Program(#[from] wfassoc::highlevel::ProgramError),
|
Program(#[from] wfassoc::highlevel::ProgramError),
|
||||||
|
|
||||||
/// Error when manipulating with C-style string.
|
/// Error when manipulating with C-style string.
|
||||||
#[error("{0}")]
|
#[error("C-Style string FFI error: {0}")]
|
||||||
CStrFfi(#[from] cstr_ffi::Error),
|
CStrFfi(#[from] cstr_ffi::Error),
|
||||||
/// Error when manipulating with object pool.
|
/// Error when manipulating with object pool.
|
||||||
#[error("{0}")]
|
#[error("object pool error: {0}")]
|
||||||
ObjectPool(#[from] object_pool::Error),
|
ObjectPool(#[from] object_pool::Error),
|
||||||
|
|
||||||
/// Error occurs when checking enum value
|
/// Error occurs when checking enum value
|
||||||
#[error("")]
|
#[error("the enumeration value provided to FFI function is out of its range")]
|
||||||
EnumOutOfRange,
|
EnumOutOfRange,
|
||||||
/// Error when manipulating with poison RwLock
|
/// Error when manipulating with poison RwLock
|
||||||
#[error("RwLock is poisoning")]
|
#[error("concurrency error: RwLock is poisonous")]
|
||||||
PoisonRwLock,
|
PoisonRwLock,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,19 +191,22 @@ static PROGRAM_POOL: LazyLock<RwLock<ObjectPool<Program>>> =
|
|||||||
static EXT_STATUS_POOL: LazyLock<RwLock<ObjectPool<wfassoc::highlevel::ProgramExtStatus>>> =
|
static EXT_STATUS_POOL: LazyLock<RwLock<ObjectPool<wfassoc::highlevel::ProgramExtStatus>>> =
|
||||||
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
static ICON_RC_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::IconRc>>> =
|
static SELF_EXT_STATUS_POOL: LazyLock<
|
||||||
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
RwLock<ObjectPool<wfassoc::highlevel::ProgramSelfExtStatus>>,
|
||||||
|
> = LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
static EXT_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::Ext>>> =
|
static ICON_RC_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::IconRc>>> =
|
||||||
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
LazyLock::new(|| RwLock::new(ObjectPool::new()));
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: Exposed Types
|
// region: Exposed Types
|
||||||
|
|
||||||
pub use cstr_ffi::CStyleString;
|
pub use ffi_types::{
|
||||||
pub use ffi_types::{HICON, Scope, View};
|
CStyleString as WFCString, HICON as WFHICON, Scope as WFScope, View as WFView,
|
||||||
pub use object_pool::Token;
|
};
|
||||||
|
pub use ffi_types::{INVALID_HICON as WF_INVALID_HICON, INVALID_INDEX as WF_INVALID_INDEX};
|
||||||
|
pub use object_pool::Token as WFToken;
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
@@ -218,8 +221,8 @@ pub extern "C" fn WFStartup() -> bool {
|
|||||||
let _pool = pull_writer!(SCHEMA_POOL)?;
|
let _pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let _pool = pull_writer!(PROGRAM_POOL)?;
|
let _pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let _pool = pull_writer!(EXT_STATUS_POOL)?;
|
let _pool = pull_writer!(EXT_STATUS_POOL)?;
|
||||||
|
let _pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
||||||
let _pool = pull_writer!(ICON_RC_POOL)?;
|
let _pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
let _pool = pull_writer!(EXT_POOL)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -234,16 +237,16 @@ pub extern "C" fn WFShutdown() -> bool {
|
|||||||
pool.clear();
|
pool.clear();
|
||||||
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
|
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
|
||||||
pool.clear();
|
pool.clear();
|
||||||
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
||||||
pool.clear();
|
pool.clear();
|
||||||
let mut pool = pull_writer!(EXT_POOL)?;
|
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
pool.clear();
|
pool.clear();
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFGetLastError() -> CStyleString {
|
pub extern "C" fn WFGetLastError() -> WFCString {
|
||||||
last_error::get_last_error()
|
last_error::get_last_error()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +256,7 @@ pub extern "C" fn WFHasPrivilege() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFInvalidToken() -> Token {
|
pub extern "C" fn WFInvalidToken() -> WFToken {
|
||||||
object_pool::invalid_token()
|
object_pool::invalid_token()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,16 +265,16 @@ pub extern "C" fn WFInvalidToken() -> Token {
|
|||||||
// region: Schema
|
// region: Schema
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaCreate(out_schema: out_param_ty!(Token)) -> bool {
|
pub extern "C" fn WFSchemaCreate(out_schema: out_param_ty!(WFToken)) -> bool {
|
||||||
cffi_wrapper!(|| -> (out_schema: Token) {
|
cffi_wrapper!(|| -> (out_schema: WFToken) {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
Ok(pool.allocate(Schema::new())?)
|
Ok(pool.allocate(Schema::new())?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaDestroy(in_schema: in_param_ty!(Token)) -> bool {
|
pub extern "C" fn WFSchemaDestroy(in_schema: in_param_ty!(WFToken)) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token| {
|
cffi_wrapper!(|in_schema: WFToken| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
Ok(pool.free(in_schema)?)
|
Ok(pool.free(in_schema)?)
|
||||||
})
|
})
|
||||||
@@ -279,10 +282,10 @@ pub extern "C" fn WFSchemaDestroy(in_schema: in_param_ty!(Token)) -> bool {
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaSetIdentifier(
|
pub extern "C" fn WFSchemaSetIdentifier(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
cffi_wrapper!(|in_schema: WFToken, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
schema.set_identifier(cstr_ffi::parse_ffi_string(in_value)?);
|
schema.set_identifier(cstr_ffi::parse_ffi_string(in_value)?);
|
||||||
@@ -292,10 +295,10 @@ pub extern "C" fn WFSchemaSetIdentifier(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaSetPath(
|
pub extern "C" fn WFSchemaSetPath(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
cffi_wrapper!(|in_schema: WFToken, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
schema.set_path(cstr_ffi::parse_ffi_string(in_value)?);
|
schema.set_path(cstr_ffi::parse_ffi_string(in_value)?);
|
||||||
@@ -305,10 +308,10 @@ pub extern "C" fn WFSchemaSetPath(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaSetClsid(
|
pub extern "C" fn WFSchemaSetClsid(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
cffi_wrapper!(|in_schema: WFToken, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
schema.set_clsid(cstr_ffi::parse_ffi_string(in_value)?);
|
schema.set_clsid(cstr_ffi::parse_ffi_string(in_value)?);
|
||||||
@@ -318,10 +321,10 @@ pub extern "C" fn WFSchemaSetClsid(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaSetName(
|
pub extern "C" fn WFSchemaSetName(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
cffi_wrapper!(|in_schema: WFToken, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
|
||||||
@@ -337,10 +340,10 @@ pub extern "C" fn WFSchemaSetName(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaSetIcon(
|
pub extern "C" fn WFSchemaSetIcon(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
cffi_wrapper!(|in_schema: WFToken, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
|
||||||
@@ -356,10 +359,10 @@ pub extern "C" fn WFSchemaSetIcon(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaSetBehavior(
|
pub extern "C" fn WFSchemaSetBehavior(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token, in_value: CStyleString| {
|
cffi_wrapper!(|in_schema: WFToken, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
|
|
||||||
@@ -375,12 +378,12 @@ pub extern "C" fn WFSchemaSetBehavior(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaAddStr(
|
pub extern "C" fn WFSchemaAddStr(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_name: in_param_ty!(CStyleString),
|
in_name: in_param_ty!(WFCString),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(
|
cffi_wrapper!(
|
||||||
|in_schema: Token, in_name: CStyleString, in_value: CStyleString| {
|
|in_schema: WFToken, in_name: WFCString, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
schema.add_str(
|
schema.add_str(
|
||||||
@@ -394,12 +397,12 @@ pub extern "C" fn WFSchemaAddStr(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaAddIcon(
|
pub extern "C" fn WFSchemaAddIcon(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_name: in_param_ty!(CStyleString),
|
in_name: in_param_ty!(WFCString),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(
|
cffi_wrapper!(
|
||||||
|in_schema: Token, in_name: CStyleString, in_value: CStyleString| {
|
|in_schema: WFToken, in_name: WFCString, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
schema.add_icon(
|
schema.add_icon(
|
||||||
@@ -413,12 +416,12 @@ pub extern "C" fn WFSchemaAddIcon(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaAddBehavior(
|
pub extern "C" fn WFSchemaAddBehavior(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_name: in_param_ty!(CStyleString),
|
in_name: in_param_ty!(WFCString),
|
||||||
in_value: in_param_ty!(CStyleString),
|
in_value: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(
|
cffi_wrapper!(
|
||||||
|in_schema: Token, in_name: CStyleString, in_value: CStyleString| {
|
|in_schema: WFToken, in_name: WFCString, in_value: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
schema.add_behavior(
|
schema.add_behavior(
|
||||||
@@ -432,17 +435,17 @@ pub extern "C" fn WFSchemaAddBehavior(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSchemaAddExt(
|
pub extern "C" fn WFSchemaAddExt(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
in_ext: in_param_ty!(CStyleString),
|
in_ext: in_param_ty!(WFCString),
|
||||||
in_ext_name: in_param_ty!(CStyleString),
|
in_ext_name: in_param_ty!(WFCString),
|
||||||
in_ext_icon: in_param_ty!(CStyleString),
|
in_ext_icon: in_param_ty!(WFCString),
|
||||||
in_ext_behavior: in_param_ty!(CStyleString),
|
in_ext_behavior: in_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token,
|
cffi_wrapper!(|in_schema: WFToken,
|
||||||
in_ext: CStyleString,
|
in_ext: WFCString,
|
||||||
in_ext_name: CStyleString,
|
in_ext_name: WFCString,
|
||||||
in_ext_icon: CStyleString,
|
in_ext_icon: WFCString,
|
||||||
in_ext_behavior: CStyleString| {
|
in_ext_behavior: WFCString| {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.get_mut(in_schema)?;
|
let schema = pool.get_mut(in_schema)?;
|
||||||
schema.add_ext(
|
schema.add_ext(
|
||||||
@@ -461,10 +464,10 @@ pub extern "C" fn WFSchemaAddExt(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramCreate(
|
pub extern "C" fn WFProgramCreate(
|
||||||
in_schema: in_param_ty!(Token),
|
in_schema: in_param_ty!(WFToken),
|
||||||
out_program: out_param_ty!(Token),
|
out_program: out_param_ty!(WFToken),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_schema: Token| -> (out_program: Token) {
|
cffi_wrapper!(|in_schema: WFToken| -> (out_program: WFToken) {
|
||||||
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
let mut pool = pull_writer!(SCHEMA_POOL)?;
|
||||||
let schema = pool.pop(in_schema)?;
|
let schema = pool.pop(in_schema)?;
|
||||||
|
|
||||||
@@ -475,8 +478,8 @@ pub extern "C" fn WFProgramCreate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramDestroy(in_program: in_param_ty!(Token)) -> bool {
|
pub extern "C" fn WFProgramDestroy(in_program: in_param_ty!(WFToken)) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token| {
|
cffi_wrapper!(|in_program: WFToken| {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
Ok(pool.free(in_program)?)
|
Ok(pool.free(in_program)?)
|
||||||
})
|
})
|
||||||
@@ -484,106 +487,91 @@ pub extern "C" fn WFProgramDestroy(in_program: in_param_ty!(Token)) -> bool {
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramResolveName(
|
pub extern "C" fn WFProgramResolveName(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
out_name: out_param_ty!(CStyleString),
|
out_name: out_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token| -> (out_name: CStyleString) {
|
cffi_wrapper!(|in_program: WFToken| -> (out_name: WFCString) {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
|
|
||||||
let name = match program.resolve_name()? {
|
let name = program.resolve_name()?;
|
||||||
Some(name) => {
|
cstr_ffi::set_ffi_string(&name)?;
|
||||||
cstr_ffi::set_ffi_string(&name)?;
|
Ok(cstr_ffi::get_ffi_string())
|
||||||
cstr_ffi::get_ffi_string()
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
std::ptr::null()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(name)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramResolveIcon(
|
pub extern "C" fn WFProgramResolveIcon(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
out_icon_rc: out_param_ty!(Token),
|
out_icon_rc: out_param_ty!(WFToken),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token| -> (out_icon_rc: Token) {
|
cffi_wrapper!(|in_program: WFToken| -> (out_icon_rc: WFToken) {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
|
|
||||||
let icon = program.resolve_icon()?;
|
let icon = program.resolve_icon()?;
|
||||||
let token = match icon {
|
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
Some(icon) => {
|
Ok(pool.allocate(icon)?)
|
||||||
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
|
||||||
pool.allocate(icon)?
|
|
||||||
},
|
|
||||||
None => {
|
|
||||||
object_pool::invalid_token()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(token)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramExtsLen(
|
pub extern "C" fn WFProgramExtsLen(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
out_len: out_param_ty!(usize),
|
out_len: out_param_ty!(usize),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token| -> (out_len: usize) {
|
cffi_wrapper!(|in_program: WFToken| -> (out_len: usize) {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
Ok(program.exts_len())
|
Ok(program.exts_len())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
pub extern "C" fn WFProgramGetExt(
|
|
||||||
in_program: in_param_ty!(Token),
|
|
||||||
in_index: in_param_ty!(usize),
|
|
||||||
out_ext: out_param_ty!(Token),
|
|
||||||
) -> bool {
|
|
||||||
cffi_wrapper!(|in_program: Token, in_index: usize| -> (out_ext: Token) {
|
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
|
||||||
let program = pool.get_mut(in_program)?;
|
|
||||||
|
|
||||||
let ext = program.get_ext(in_index)?;
|
|
||||||
let mut pool = pull_writer!(EXT_POOL)?;
|
|
||||||
let token = pool.allocate(ext.clone())?;
|
|
||||||
Ok(token)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramFindExt(
|
pub extern "C" fn WFProgramFindExt(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_body: in_param_ty!(CStyleString),
|
in_body: in_param_ty!(WFCString),
|
||||||
out_index: out_param_ty!(usize),
|
out_index: out_param_ty!(usize),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_body: CStyleString| -> (out_index: usize) {
|
cffi_wrapper!(|in_program: WFToken, in_body: WFCString| -> (out_index: usize) {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
|
|
||||||
let body = cstr_ffi::parse_ffi_string(in_body)?;
|
let body = cstr_ffi::parse_ffi_string(in_body)?;
|
||||||
let index = match program.find_ext(body) {
|
let index = match program.find_ext(body) {
|
||||||
Some(index) => index,
|
Some(index) => index,
|
||||||
None => ffi_types::INVALID_INDEX,
|
None => WF_INVALID_INDEX,
|
||||||
};
|
};
|
||||||
Ok(index)
|
Ok(index)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramRegister(
|
pub extern "C" fn WFProgramResolveExt(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_scope: in_param_ty!(u32),
|
in_index: in_param_ty!(usize),
|
||||||
|
out_self_ext_status: out_param_ty!(WFToken),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_scope: u32| {
|
cffi_wrapper!(|in_program: WFToken, in_index: usize| -> (out_self_ext_status: WFToken) {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
let scope = resolve_enum!(Scope, in_scope)?;
|
|
||||||
|
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!(WFToken),
|
||||||
|
in_scope: in_param_ty!(u32),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_program: WFToken, in_scope: u32| {
|
||||||
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
|
let program = pool.get_mut(in_program)?;
|
||||||
|
let scope = resolve_enum!(WFScope, in_scope)?;
|
||||||
program.register(scope.into())?;
|
program.register(scope.into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -591,13 +579,13 @@ pub extern "C" fn WFProgramRegister(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramUnregister(
|
pub extern "C" fn WFProgramUnregister(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_scope: in_param_ty!(u32),
|
in_scope: in_param_ty!(u32),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_scope: u32| {
|
cffi_wrapper!(|in_program: WFToken, in_scope: u32| {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
let scope = resolve_enum!(Scope, in_scope)?;
|
let scope = resolve_enum!(WFScope, in_scope)?;
|
||||||
program.unregister(scope.into())?;
|
program.unregister(scope.into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -605,28 +593,28 @@ pub extern "C" fn WFProgramUnregister(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramIsRegistered(
|
pub extern "C" fn WFProgramIsRegistered(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_scope: in_param_ty!(u32),
|
in_scope: in_param_ty!(u32),
|
||||||
out_is_registered: out_param_ty!(bool),
|
out_is_registered: out_param_ty!(bool),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_scope: u32| -> (out_is_registered: bool) {
|
cffi_wrapper!(|in_program: WFToken, in_scope: u32| -> (out_is_registered: bool) {
|
||||||
let pool = pull_reader!(PROGRAM_POOL)?;
|
let pool = pull_reader!(PROGRAM_POOL)?;
|
||||||
let program = pool.get(in_program)?;
|
let program = pool.get(in_program)?;
|
||||||
let scope = resolve_enum!(Scope, in_scope)?;
|
let scope = resolve_enum!(WFScope, in_scope)?;
|
||||||
Ok(program.is_registered(scope.into())?)
|
Ok(program.is_registered(scope.into())?)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramLinkExt(
|
pub extern "C" fn WFProgramLinkExt(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_scope: in_param_ty!(u32),
|
in_scope: in_param_ty!(u32),
|
||||||
in_index: in_param_ty!(usize),
|
in_index: in_param_ty!(usize),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_scope: u32, in_index: usize| {
|
cffi_wrapper!(|in_program: WFToken, in_scope: u32, in_index: usize| {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
let scope = resolve_enum!(Scope, in_scope)?;
|
let scope = resolve_enum!(WFScope, in_scope)?;
|
||||||
program.link_ext(scope.into(), in_index)?;
|
program.link_ext(scope.into(), in_index)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -634,14 +622,14 @@ pub extern "C" fn WFProgramLinkExt(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramUnlinkExt(
|
pub extern "C" fn WFProgramUnlinkExt(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_scope: in_param_ty!(u32),
|
in_scope: in_param_ty!(u32),
|
||||||
in_index: in_param_ty!(usize),
|
in_index: in_param_ty!(usize),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_scope: u32, in_index: usize| {
|
cffi_wrapper!(|in_program: WFToken, in_scope: u32, in_index: usize| {
|
||||||
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
let mut pool = pull_writer!(PROGRAM_POOL)?;
|
||||||
let program = pool.get_mut(in_program)?;
|
let program = pool.get_mut(in_program)?;
|
||||||
let scope = resolve_enum!(Scope, in_scope)?;
|
let scope = resolve_enum!(WFScope, in_scope)?;
|
||||||
program.unlink_ext(scope.into(), in_index)?;
|
program.unlink_ext(scope.into(), in_index)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -649,15 +637,15 @@ pub extern "C" fn WFProgramUnlinkExt(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramQueryExt(
|
pub extern "C" fn WFProgramQueryExt(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_view: in_param_ty!(u32),
|
in_view: in_param_ty!(u32),
|
||||||
in_index: in_param_ty!(usize),
|
in_index: in_param_ty!(usize),
|
||||||
out_ext_status: out_param_ty!(Token),
|
out_ext_status: out_param_ty!(WFToken),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_view: u32, in_index: usize| -> (out_ext_status: Token) {
|
cffi_wrapper!(|in_program: WFToken, in_view: u32, in_index: usize| -> (out_ext_status: WFToken) {
|
||||||
let pool = pull_reader!(PROGRAM_POOL)?;
|
let pool = pull_reader!(PROGRAM_POOL)?;
|
||||||
let program = pool.get(in_program)?;
|
let program = pool.get(in_program)?;
|
||||||
let view = resolve_enum!(View, in_view)?;
|
let view = resolve_enum!(WFView, in_view)?;
|
||||||
|
|
||||||
let ext_status = program.query_ext(view.into(), in_index)?;
|
let ext_status = program.query_ext(view.into(), in_index)?;
|
||||||
let token = match ext_status {
|
let token = match ext_status {
|
||||||
@@ -676,8 +664,8 @@ pub extern "C" fn WFProgramQueryExt(
|
|||||||
// region: Extension Status
|
// region: Extension Status
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFExtStatusDestroy(in_ext_status: in_param_ty!(Token)) -> bool {
|
pub extern "C" fn WFExtStatusDestroy(in_ext_status: in_param_ty!(WFToken)) -> bool {
|
||||||
cffi_wrapper!(|in_ext_status: Token| {
|
cffi_wrapper!(|in_ext_status: WFToken| {
|
||||||
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
|
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
|
||||||
Ok(pool.free(in_ext_status)?)
|
Ok(pool.free(in_ext_status)?)
|
||||||
})
|
})
|
||||||
@@ -685,10 +673,10 @@ pub extern "C" fn WFExtStatusDestroy(in_ext_status: in_param_ty!(Token)) -> bool
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFExtStatusGetName(
|
pub extern "C" fn WFExtStatusGetName(
|
||||||
in_ext_status: in_param_ty!(Token),
|
in_ext_status: in_param_ty!(WFToken),
|
||||||
out_name: out_param_ty!(CStyleString),
|
out_name: out_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_ext_status: Token| -> (out_name: CStyleString) {
|
cffi_wrapper!(|in_ext_status: WFToken| -> (out_name: WFCString) {
|
||||||
let pool = pull_reader!(EXT_STATUS_POOL)?;
|
let pool = pull_reader!(EXT_STATUS_POOL)?;
|
||||||
let ext_status = pool.get(in_ext_status)?;
|
let ext_status = pool.get(in_ext_status)?;
|
||||||
|
|
||||||
@@ -699,18 +687,83 @@ pub extern "C" fn WFExtStatusGetName(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFExtStatusGetIcon(
|
pub extern "C" fn WFExtStatusGetIcon(
|
||||||
in_ext_status: in_param_ty!(Token),
|
in_ext_status: in_param_ty!(WFToken),
|
||||||
out_icon: out_param_ty!(HICON),
|
out_icon: out_param_ty!(WFHICON),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_ext_status: Token| -> (out_icon: HICON) {
|
cffi_wrapper!(|in_ext_status: WFToken| -> (out_icon: WFHICON) {
|
||||||
let pool = pull_reader!(EXT_STATUS_POOL)?;
|
let pool = pull_reader!(EXT_STATUS_POOL)?;
|
||||||
let ext_status = pool.get(in_ext_status)?;
|
let ext_status = pool.get(in_ext_status)?;
|
||||||
|
|
||||||
let icon = match ext_status.get_icon() {
|
let icon = ext_status.get_icon();
|
||||||
Some(icon) => icon.get_icon(),
|
Ok(icon.get_icon())
|
||||||
None => ffi_types::INVALID_HICON,
|
})
|
||||||
};
|
}
|
||||||
Ok(icon)
|
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region: Self Extension Status
|
||||||
|
|
||||||
|
#[unsafe(no_mangle)]
|
||||||
|
pub extern "C" fn WFSelfExtStatusDestroy(in_self_ext_status: in_param_ty!(WFToken)) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: WFToken| {
|
||||||
|
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!(WFToken),
|
||||||
|
out_name: out_param_ty!(WFCString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_name: WFCString) {
|
||||||
|
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!(WFToken),
|
||||||
|
out_icon: out_param_ty!(WFHICON),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_icon: WFHICON) {
|
||||||
|
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!(WFToken),
|
||||||
|
out_inner: out_param_ty!(WFCString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_inner: WFCString) {
|
||||||
|
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!(WFToken),
|
||||||
|
out_inner: out_param_ty!(WFCString),
|
||||||
|
) -> bool {
|
||||||
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_inner: WFCString) {
|
||||||
|
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())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -719,8 +772,8 @@ pub extern "C" fn WFExtStatusGetIcon(
|
|||||||
// region: Icon Resource
|
// region: Icon Resource
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFIconRcDestroy(in_icon_rc: in_param_ty!(Token)) -> bool {
|
pub extern "C" fn WFIconRcDestroy(in_icon_rc: in_param_ty!(WFToken)) -> bool {
|
||||||
cffi_wrapper!(|in_icon_rc: Token| {
|
cffi_wrapper!(|in_icon_rc: WFToken| {
|
||||||
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
let mut pool = pull_writer!(ICON_RC_POOL)?;
|
||||||
Ok(pool.free(in_icon_rc)?)
|
Ok(pool.free(in_icon_rc)?)
|
||||||
})
|
})
|
||||||
@@ -728,10 +781,10 @@ pub extern "C" fn WFIconRcDestroy(in_icon_rc: in_param_ty!(Token)) -> bool {
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFIconRcGetIcon(
|
pub extern "C" fn WFIconRcGetIcon(
|
||||||
in_icon_rc: in_param_ty!(Token),
|
in_icon_rc: in_param_ty!(WFToken),
|
||||||
out_icon: out_param_ty!(HICON),
|
out_icon: out_param_ty!(WFHICON),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_icon_rc: Token| -> (out_icon: HICON) {
|
cffi_wrapper!(|in_icon_rc: WFToken| -> (out_icon: WFHICON) {
|
||||||
let pool = pull_reader!(ICON_RC_POOL)?;
|
let pool = pull_reader!(ICON_RC_POOL)?;
|
||||||
let icon_rc = pool.get(in_icon_rc)?;
|
let icon_rc = pool.get(in_icon_rc)?;
|
||||||
|
|
||||||
@@ -740,41 +793,3 @@ pub extern "C" fn WFIconRcGetIcon(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region: File Extension
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
pub extern "C" fn WFExtDestroy(in_ext: in_param_ty!(Token)) -> bool {
|
|
||||||
cffi_wrapper!(|in_ext: Token| {
|
|
||||||
let mut pool = pull_writer!(EXT_POOL)?;
|
|
||||||
Ok(pool.free(in_ext)?)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
pub extern "C" fn WFExtGetInner(
|
|
||||||
in_ext: in_param_ty!(Token),
|
|
||||||
out_inner: out_param_ty!(CStyleString),
|
|
||||||
) -> bool {
|
|
||||||
cffi_wrapper!(|in_ext: Token| -> (out_inner: CStyleString) {
|
|
||||||
let pool = pull_reader!(EXT_POOL)?;
|
|
||||||
let ext = pool.get(in_ext)?;
|
|
||||||
cstr_ffi::set_ffi_string(ext.inner())?;
|
|
||||||
Ok(cstr_ffi::get_ffi_string())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
|
||||||
pub extern "C" fn WFExtGetDottedInner(
|
|
||||||
in_ext: in_param_ty!(Token),
|
|
||||||
out_inner: out_param_ty!(CStyleString),
|
|
||||||
) -> bool {
|
|
||||||
cffi_wrapper!(|in_ext: Token| -> (out_inner: CStyleString) {
|
|
||||||
let pool = pull_reader!(EXT_POOL)?;
|
|
||||||
let ext = pool.get(in_ext)?;
|
|
||||||
cstr_ffi::set_ffi_string(&ext.dotted_inner())?;
|
|
||||||
Ok(cstr_ffi::get_ffi_string())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Wfassoc Executable
|
# WFAssoc Executable
|
||||||
|
|
||||||
For the syntax of this executable's parameters, execute it with `--help` for more.
|
For the syntax of this executable's parameters, execute it with `--help` for more.
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl Manifest {
|
|||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
pub enum ParseSchemaError {
|
pub enum ParseSchemaError {
|
||||||
/// Error when operating with schema.
|
/// Error when operating with schema.
|
||||||
#[error("{0}")]
|
#[error("failed on parsing manifest file to Schema: {0}")]
|
||||||
Schema(#[from] wfassoc::highlevel::SchemaError)
|
Schema(#[from] wfassoc::highlevel::SchemaError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+18
-14
@@ -12,20 +12,21 @@ use toml;
|
|||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Error when parsing Manifest TOML file.
|
/// Error when parsing Manifest TOML file.
|
||||||
#[error("{0}")]
|
#[error("initialization error: {0}")]
|
||||||
ParseManifest(#[from] manifest::ParseManifestError),
|
ParseManifest(#[from] manifest::ParseManifestError),
|
||||||
/// Error when parsing Manifest into Schema.
|
/// Error when parsing Manifest into Schema.
|
||||||
#[error("{0}")]
|
#[error("initialization error: {0}")]
|
||||||
ParseSchema(#[from] manifest::ParseSchemaError),
|
ParseSchema(#[from] manifest::ParseSchemaError),
|
||||||
|
/// Error when serializing TOML
|
||||||
|
#[error("initialization error: {0}")]
|
||||||
|
SerializeToml(#[from] toml::ser::Error),
|
||||||
|
|
||||||
/// Error when parsing Schema into Program.
|
/// Error when parsing Schema into Program.
|
||||||
#[error("{0}")]
|
#[error("wfassoc error: {0}")]
|
||||||
ParseProgram(#[from] wfassoc::highlevel::ParseProgramError),
|
ParseProgram(#[from] wfassoc::highlevel::ParseProgramError),
|
||||||
/// Error when operating Program.
|
/// Error when operating Program.
|
||||||
#[error("{0}")]
|
#[error("wfassoc 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
|
/// Find duplicated name when converting extension name to index
|
||||||
#[error("given extension name {0} has been specified more than one time")]
|
#[error("given extension name {0} has been specified more than one time")]
|
||||||
@@ -34,8 +35,8 @@ pub enum Error {
|
|||||||
#[error("given extension name {0} is not presented in application")]
|
#[error("given extension name {0} is not presented in application")]
|
||||||
BadExtName(String),
|
BadExtName(String),
|
||||||
/// Find star (*) extension name with other extension names when converting extension name to index
|
/// 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")]
|
#[error(r#"wildcard extension name "*" is not allowed to be used with other extension names"#)]
|
||||||
ExclusiveStarExtName(String),
|
ExclusiveStarExtName,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Result type used in this module.
|
/// Result type used in this module.
|
||||||
@@ -60,7 +61,7 @@ fn stringified_exts_to_indices(
|
|||||||
// Check for star (*) with other extensions
|
// Check for star (*) with other extensions
|
||||||
let has_star = exts.iter().any(|ext| ext == "*");
|
let has_star = exts.iter().any(|ext| ext == "*");
|
||||||
if has_star && exts.len() > 1 {
|
if has_star && exts.len() > 1 {
|
||||||
return Err(Error::ExclusiveStarExtName("*".to_string()));
|
return Err(Error::ExclusiveStarExtName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If star is present alone, return fixed list from zero to the maximum ext index.
|
// If star is present alone, return fixed list from zero to the maximum ext index.
|
||||||
@@ -124,9 +125,9 @@ fn run_ext_unlink(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let exts = stringified_exts_to_indices(&program, exts)?;
|
let exts = stringified_exts_to_indices(&program, exts)?;
|
||||||
for index in exts {
|
for index in exts {
|
||||||
program.link_ext(scope, index)?;
|
program.unlink_ext(scope, index)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("File extension now is unlinked.");
|
println!("File extension now is unlinked.");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -139,9 +140,12 @@ fn run_ext_list(
|
|||||||
// Fetch info
|
// Fetch info
|
||||||
let mut ext_list: HashMap<String, Option<String>> = HashMap::new();
|
let mut ext_list: HashMap<String, Option<String>> = HashMap::new();
|
||||||
for index in 0..program.exts_len() {
|
for index in 0..program.exts_len() {
|
||||||
let ext = program.get_ext(index)?;
|
let self_ext_status = program.resolve_ext(index)?;
|
||||||
let status = program.query_ext(view, index)?;
|
let status = program.query_ext(view, index)?;
|
||||||
ext_list.insert(ext.dotted_inner(), status.map(|s| s.get_name().to_string()));
|
ext_list.insert(
|
||||||
|
self_ext_status.get_dotted_ext(),
|
||||||
|
status.map(|s| s.get_name().to_string()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output by styles
|
// Output by styles
|
||||||
|
|||||||
+21
-1
@@ -1,4 +1,6 @@
|
|||||||
# 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.
|
||||||
@@ -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 highly 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,3 +1,5 @@
|
|||||||
|
//! The module gives a convenient highlevel wrapper for lowlevel file association operations.
|
||||||
|
|
||||||
use crate::lowlevel;
|
use crate::lowlevel;
|
||||||
|
|
||||||
// region: Utilities
|
// region: Utilities
|
||||||
@@ -27,7 +29,7 @@ mod schema;
|
|||||||
mod program;
|
mod program;
|
||||||
|
|
||||||
pub use schema::{Schema, SchemaError};
|
pub use schema::{Schema, SchemaError};
|
||||||
pub use program::{Program, ParseProgramError, ProgramError, ProgramExtStatus};
|
pub use program::{Program, ParseProgramError, ProgramError, ProgramSelfExtStatus, ProgramExtStatus};
|
||||||
pub use lowlevel::{Scope, View};
|
pub use lowlevel::{Scope, View};
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
@@ -17,35 +17,38 @@ use thiserror::Error as TeError;
|
|||||||
/// Error occurs when trying converting [Schema] into [Program].
|
/// Error occurs when trying converting [Schema] into [Program].
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
pub enum ParseProgramError {
|
pub enum ParseProgramError {
|
||||||
#[error("{0}")]
|
#[error("parsing into Program error: {0}")]
|
||||||
BadExtBody(#[from] concept::BadExtBodyError),
|
BadExtBody(#[from] concept::BadExtBodyError),
|
||||||
#[error("{0}")]
|
#[error("parsing into Program error: {0}")]
|
||||||
BadProgIdPart(#[from] concept::BadProgIdPartError),
|
BadProgIdPart(#[from] concept::BadProgIdPartError),
|
||||||
#[error("{0}")]
|
#[error("parsing into Program error: {0}")]
|
||||||
BadFileName(#[from] concept::BadFileNameError),
|
BadFileName(#[from] concept::BadFileNameError),
|
||||||
#[error("{0}")]
|
#[error("parsing into Program error: {0}")]
|
||||||
|
ParseClsid(#[from] concept::ParseClsidError),
|
||||||
|
#[error("parsing into Program error: {0}")]
|
||||||
ParseCmdLine(#[from] concept::ParseCmdLineError),
|
ParseCmdLine(#[from] concept::ParseCmdLineError),
|
||||||
#[error("{0}")]
|
#[error("parsing into Program error: {0}")]
|
||||||
CastOsStr(#[from] utilities::CastOsStrError),
|
CastOsStr(#[from] utilities::CastOsStrError),
|
||||||
#[error("given path doesn't has legal file name part")]
|
|
||||||
|
#[error("given path doesn't has legal file name part when parsing into Program")]
|
||||||
NoFileNamePart,
|
NoFileNamePart,
|
||||||
#[error("given path doesn't has legal directory part")]
|
#[error("given path doesn't has legal directory part when parsing into Program")]
|
||||||
NoDirNamePart,
|
NoDirNamePart,
|
||||||
#[error("given identifier is not presented in dict")]
|
#[error("identifier {0} is unexpected not presented in HashMap when parsing into Program")]
|
||||||
NoSuchIdentifier,
|
NoSuchIdentifier(String),
|
||||||
#[error("extension name should not be empty")]
|
#[error("bad program identifier found when parsing into Program")]
|
||||||
EmptyExtension,
|
|
||||||
#[error("given program identifier is not allowed")]
|
|
||||||
BadIdentifier,
|
BadIdentifier,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error occurs when operating with [Program].
|
/// Error occurs when operating with [Program].
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
pub enum ProgramError {
|
pub enum ProgramError {
|
||||||
#[error("{0}")]
|
#[error("Program operation error: {0}")]
|
||||||
Lowlevel(#[from] lowlevel::Error),
|
Lowlevel(#[from] lowlevel::Error),
|
||||||
#[error("given index is invalid")]
|
#[error("Program operation error: {0}")]
|
||||||
BadIndex,
|
LoadIconRc(#[from] concept::LoadIconRcError),
|
||||||
|
#[error("given file extension index {0} is invalid when visiting Program")]
|
||||||
|
BadIndex(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@@ -53,11 +56,18 @@ pub enum ProgramError {
|
|||||||
// region: Program
|
// region: Program
|
||||||
|
|
||||||
/// Program is a complete and immutable program representer
|
/// Program is a complete and immutable program representer
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Program {
|
pub struct Program {
|
||||||
app_paths_key: lowlevel::AppPathsKey,
|
app_paths_key: lowlevel::AppPathsKey,
|
||||||
applications_key: lowlevel::ApplicationsKey,
|
applications_key: lowlevel::ApplicationsKey,
|
||||||
app_path: String,
|
app_path: String,
|
||||||
|
app_file_name: String,
|
||||||
app_dir_path: String,
|
app_dir_path: String,
|
||||||
|
|
||||||
|
// TODO: Remove this dead_code attribute once we start to use CLSID.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
clsid: concept::Clsid,
|
||||||
|
|
||||||
name: Option<Arc<ProgramStr>>,
|
name: Option<Arc<ProgramStr>>,
|
||||||
icon: Option<Arc<ProgramIcon>>,
|
icon: Option<Arc<ProgramIcon>>,
|
||||||
behavior: Option<Arc<ProgramBehavior>>,
|
behavior: Option<Arc<ProgramBehavior>>,
|
||||||
@@ -99,6 +109,8 @@ impl Program {
|
|||||||
.ok_or(ParseProgramError::NoDirNamePart)
|
.ok_or(ParseProgramError::NoDirNamePart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Flatten a `HashMap<String, V>` into a `Vec<U>` with a reverse index map,
|
||||||
|
/// by applying the given transformation function `f` to each value.
|
||||||
fn flat_hashmap<V, U, F>(
|
fn flat_hashmap<V, U, F>(
|
||||||
hashmap: &HashMap<String, V>,
|
hashmap: &HashMap<String, V>,
|
||||||
f: F,
|
f: F,
|
||||||
@@ -115,6 +127,8 @@ impl Program {
|
|||||||
Ok((vector, indexmap))
|
Ok((vector, indexmap))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve a key string to an `Arc<T>` by looking it up in the index map,
|
||||||
|
/// then indexing into the vector.
|
||||||
fn resolve_index<T>(
|
fn resolve_index<T>(
|
||||||
key: &str,
|
key: &str,
|
||||||
vector: &Vec<Arc<T>>,
|
vector: &Vec<Arc<T>>,
|
||||||
@@ -125,7 +139,7 @@ impl Program {
|
|||||||
.get(*index)
|
.get(*index)
|
||||||
.expect("unexpected invalid index")
|
.expect("unexpected invalid index")
|
||||||
.clone()),
|
.clone()),
|
||||||
None => Err(ParseProgramError::NoSuchIdentifier),
|
None => Err(ParseProgramError::NoSuchIdentifier(key.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +186,9 @@ impl Program {
|
|||||||
let app_paths_key = lowlevel::AppPathsKey::new(key.clone());
|
let app_paths_key = lowlevel::AppPathsKey::new(key.clone());
|
||||||
let applications_key = lowlevel::ApplicationsKey::new(key.clone());
|
let applications_key = lowlevel::ApplicationsKey::new(key.clone());
|
||||||
|
|
||||||
|
// Parse CLSID
|
||||||
|
let clsid = schema.get_clsid().parse::<concept::Clsid>()?;
|
||||||
|
|
||||||
// Build string, icon and behavior list,
|
// Build string, icon and behavior list,
|
||||||
// and build mapper at the same time.
|
// and build mapper at the same time.
|
||||||
let (strs, strs_index_map) = Self::flat_hashmap(schema.get_strs(), |entry| {
|
let (strs, strs_index_map) = Self::flat_hashmap(schema.get_strs(), |entry| {
|
||||||
@@ -254,7 +271,9 @@ impl Program {
|
|||||||
app_paths_key,
|
app_paths_key,
|
||||||
applications_key,
|
applications_key,
|
||||||
app_path,
|
app_path,
|
||||||
|
app_file_name,
|
||||||
app_dir_path,
|
app_dir_path,
|
||||||
|
clsid,
|
||||||
name,
|
name,
|
||||||
icon,
|
icon,
|
||||||
behavior,
|
behavior,
|
||||||
@@ -268,39 +287,100 @@ impl Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
pub fn resolve_name(&self) -> Result<Option<String>, ProgramError> {
|
/// Resolve the display name of this application.
|
||||||
Ok(self
|
///
|
||||||
|
/// Returns the user-specified display name first.
|
||||||
|
/// Falls back to the executable file name if no display name is configured.
|
||||||
|
pub fn resolve_name(&self) -> Result<String, ProgramError> {
|
||||||
|
// Fecch from user specified name first
|
||||||
|
let name = self
|
||||||
.name
|
.name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|name| name.inner.extract().ok())
|
.map(|name| name.inner.extract().ok())
|
||||||
.flatten())
|
.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<Option<concept::IconRc>, ProgramError> {
|
/// Resolve the icon of this application.
|
||||||
Ok(self
|
///
|
||||||
|
/// Returns the user-specified icon first, then falls back to the first icon from the executable,
|
||||||
|
/// and finally to the system default executable icon.
|
||||||
|
pub fn resolve_icon(&self) -> Result<concept::IconRc, ProgramError> {
|
||||||
|
// Fetch from user specified icon first
|
||||||
|
let icon = self
|
||||||
.icon
|
.icon
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|icon| icon.inner.extract(concept::IconSizeKind::Small).ok())
|
.map(|icon| icon.inner.extract(concept::IconSizeKind::Small).ok())
|
||||||
.flatten())
|
.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,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the number of file extensions associated with this program.
|
||||||
pub fn exts_len(&self) -> usize {
|
pub fn exts_len(&self) -> usize {
|
||||||
self.ext_keys.len()
|
self.ext_keys.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_ext(&self, index: usize) -> Result<&concept::Ext, ProgramError> {
|
/// Find the index of a file extension by its body (without leading dot).
|
||||||
match self.ext_keys.get(index) {
|
///
|
||||||
Some(program_key) => {
|
/// Returns `None` if the given extension is not associated with this program.
|
||||||
let ext_key = &program_key.ext_key;
|
|
||||||
Ok(ext_key.inner())
|
|
||||||
}
|
|
||||||
None => Err(ProgramError::BadIndex),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_ext(&self, body: &str) -> Option<usize> {
|
pub fn find_ext(&self, body: &str) -> Option<usize> {
|
||||||
self.ext_keys_map.get(body).copied()
|
self.ext_keys_map.get(body).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve the extension status for the extension at the given index.
|
||||||
|
/// This status including the ProgId associated with this file extension,
|
||||||
|
/// and the display name and icon for this file extension.
|
||||||
|
///
|
||||||
|
/// Returns the fetched status, or the error occurs when fetching.
|
||||||
|
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(index))?;
|
||||||
|
|
||||||
|
// 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 {
|
impl Program {
|
||||||
@@ -341,7 +421,7 @@ impl Program {
|
|||||||
for program_key in &mut self.ext_keys {
|
for program_key in &mut self.ext_keys {
|
||||||
let progid_key = &mut program_key.progid_key;
|
let progid_key = &mut program_key.progid_key;
|
||||||
debug_println!(
|
debug_println!(
|
||||||
"Adding ProgId \"{0}\" subkey...",
|
r#"Adding ProgId "{0}" subkey..."#,
|
||||||
progid_key.inner().to_string()
|
progid_key.inner().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -356,6 +436,7 @@ impl Program {
|
|||||||
|
|
||||||
// Add this progid to file extension "open with" list.
|
// Add this progid to file extension "open with" list.
|
||||||
let ext_key = &mut program_key.ext_key;
|
let ext_key = &mut program_key.ext_key;
|
||||||
|
ext_key.ensure(scope)?;
|
||||||
ext_key.add_into_open_with_progids(scope, progid_key.inner())?;
|
ext_key.add_into_open_with_progids(scope, progid_key.inner())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +464,7 @@ impl Program {
|
|||||||
for program_key in &mut self.ext_keys {
|
for program_key in &mut self.ext_keys {
|
||||||
let progid_key = &mut program_key.progid_key;
|
let progid_key = &mut program_key.progid_key;
|
||||||
debug_println!(
|
debug_println!(
|
||||||
"Deleting ProgId \"{0}\" subkey...",
|
r#"Deleting ProgId "{0}" subkey..."#,
|
||||||
progid_key.inner().to_string()
|
progid_key.inner().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -392,9 +473,12 @@ impl Program {
|
|||||||
// there is no need to reset the default open way of file extension.
|
// there is no need to reset the default open way of file extension.
|
||||||
// So we simply remove it from "open with" list.
|
// So we simply remove it from "open with" list.
|
||||||
|
|
||||||
// Remove this ProgId from file extension "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;
|
let ext_key = &mut program_key.ext_key;
|
||||||
ext_key.remove_from_open_with_progids(scope, progid_key.inner())?;
|
if ext_key.is_exist(scope.into())? {
|
||||||
|
ext_key.remove_from_open_with_progids(scope, progid_key.inner())?;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete ProgId subkey
|
// Delete ProgId subkey
|
||||||
progid_key.delete(scope)?;
|
progid_key.delete(scope)?;
|
||||||
@@ -409,8 +493,7 @@ impl Program {
|
|||||||
/// Check whether this application has been registered in given view.
|
/// Check whether this application has been registered in given view.
|
||||||
///
|
///
|
||||||
/// Please note that this is a rough check and do not validate any data.
|
/// Please note that this is a rough check and do not validate any data.
|
||||||
///
|
/// The return value only ensures the pre-requirements of [Self::register] and [Self::unregister].
|
||||||
/// The return value only ensures the pre-requirement of `register` and `unregister`.
|
|
||||||
pub fn is_registered(&self, scope: Scope) -> Result<bool, ProgramError> {
|
pub fn is_registered(&self, scope: Scope) -> Result<bool, ProgramError> {
|
||||||
// Check App Paths subkey.
|
// Check App Paths subkey.
|
||||||
debug_println!("Checking App Paths subkey...");
|
debug_println!("Checking App Paths subkey...");
|
||||||
@@ -429,7 +512,7 @@ impl Program {
|
|||||||
for program_key in &self.ext_keys {
|
for program_key in &self.ext_keys {
|
||||||
let progid_key = &program_key.progid_key;
|
let progid_key = &program_key.progid_key;
|
||||||
debug_println!(
|
debug_println!(
|
||||||
"Checking ProgId \"{0}\" subkey...",
|
r#"Checking ProgId "{0}" subkey..."#,
|
||||||
progid_key.inner().to_string()
|
progid_key.inner().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -442,13 +525,14 @@ impl Program {
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set this program as the default handler for the file extension at the given index.
|
||||||
pub fn link_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
pub fn link_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
||||||
match self.ext_keys.get_mut(index) {
|
match self.ext_keys.get_mut(index) {
|
||||||
Some(program_key) => {
|
Some(program_key) => {
|
||||||
let ext_key = &mut program_key.ext_key;
|
let ext_key = &mut program_key.ext_key;
|
||||||
let progid_key = &program_key.progid_key;
|
let progid_key = &program_key.progid_key;
|
||||||
debug_println!(
|
debug_println!(
|
||||||
"Linking ProgId \"{0}\" to extension \"{1}\" subkey...",
|
r#"Linking ProgId "{0}" to extension "{1}" subkey..."#,
|
||||||
progid_key.inner().to_string(),
|
progid_key.inner().to_string(),
|
||||||
ext_key.inner().to_string()
|
ext_key.inner().to_string()
|
||||||
);
|
);
|
||||||
@@ -457,7 +541,7 @@ impl Program {
|
|||||||
ext_key.ensure(scope)?;
|
ext_key.ensure(scope)?;
|
||||||
ext_key.set_default(scope, Some(progid_key.inner()))?;
|
ext_key.set_default(scope, Some(progid_key.inner()))?;
|
||||||
}
|
}
|
||||||
None => return Err(ProgramError::BadIndex),
|
None => return Err(ProgramError::BadIndex(index)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Everything is okey.
|
// Everything is okey.
|
||||||
@@ -466,12 +550,13 @@ impl Program {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove this program as the default handler for the file extension at the given index.
|
||||||
pub fn unlink_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
pub fn unlink_ext(&mut self, scope: Scope, index: usize) -> Result<(), ProgramError> {
|
||||||
match self.ext_keys.get_mut(index) {
|
match self.ext_keys.get_mut(index) {
|
||||||
Some(program_key) => {
|
Some(program_key) => {
|
||||||
let ext_key = &mut program_key.ext_key;
|
let ext_key = &mut program_key.ext_key;
|
||||||
debug_println!(
|
debug_println!(
|
||||||
"Unlinking for extension \"{0}\" subkey...",
|
r#"Unlinking for extension "{0}" subkey..."#,
|
||||||
ext_key.inner().to_string()
|
ext_key.inner().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -479,7 +564,7 @@ impl Program {
|
|||||||
ext_key.ensure(scope)?;
|
ext_key.ensure(scope)?;
|
||||||
ext_key.set_default(scope, None)?;
|
ext_key.set_default(scope, None)?;
|
||||||
}
|
}
|
||||||
None => return Err(ProgramError::BadIndex),
|
None => return Err(ProgramError::BadIndex(index)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything is okey.
|
// Everything is okey.
|
||||||
@@ -488,6 +573,10 @@ impl Program {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Query the current default association for the file extension at the given index.
|
||||||
|
///
|
||||||
|
/// Returns `Ok(None)` if the extension is not associated with any program,
|
||||||
|
/// or if the associated program's registration data is missing.
|
||||||
pub fn query_ext(
|
pub fn query_ext(
|
||||||
&self,
|
&self,
|
||||||
view: View,
|
view: View,
|
||||||
@@ -497,7 +586,7 @@ impl Program {
|
|||||||
Some(program_key) => {
|
Some(program_key) => {
|
||||||
let ext_key = &program_key.ext_key;
|
let ext_key = &program_key.ext_key;
|
||||||
debug_println!(
|
debug_println!(
|
||||||
"Querying for extension \"{0}\"subkey...",
|
r#"Querying for extension "{0}"subkey..."#,
|
||||||
ext_key.inner().to_string()
|
ext_key.inner().to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -535,16 +624,19 @@ impl Program {
|
|||||||
.flatten();
|
.flatten();
|
||||||
}
|
}
|
||||||
let name = name.unwrap_or(progid_key.inner().to_string());
|
let name = name.unwrap_or(progid_key.inner().to_string());
|
||||||
// Now try to fetch icon.
|
// Now try to fetch icon and fallback to system default file icon.
|
||||||
let icon = progid_key
|
let icon = progid_key
|
||||||
.get_default_icon(view)?
|
.get_default_icon(view)?
|
||||||
.map(|ico| ico.extract(concept::IconSizeKind::Small).ok())
|
.map(|ico| ico.extract(concept::IconSizeKind::Small).ok())
|
||||||
.flatten();
|
.flatten()
|
||||||
|
.unwrap_or(concept::IconRc::GENERIC_DOCUMENT(
|
||||||
|
concept::IconSizeKind::Small,
|
||||||
|
)?);
|
||||||
|
|
||||||
// Okey, return it.
|
// Okey, return it.
|
||||||
Ok(Some(ProgramExtStatus::new(name, icon)))
|
Ok(Some(ProgramExtStatus::new(name, icon)))
|
||||||
}
|
}
|
||||||
None => Err(ProgramError::BadIndex),
|
None => Err(ProgramError::BadIndex(index)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,16 +681,60 @@ struct ProgramProgIdExtKey {
|
|||||||
|
|
||||||
// region: Exposed Stuff
|
// 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 {
|
||||||
|
/// Create a new `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.
|
/// Exposed struct representing the default associated program of specific file extension.
|
||||||
///
|
///
|
||||||
/// The data including the diaplay name and icon.
|
/// The data including the diaplay name and icon.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct ProgramExtStatus {
|
pub struct ProgramExtStatus {
|
||||||
name: String,
|
name: String,
|
||||||
icon: Option<concept::IconRc>,
|
icon: concept::IconRc,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgramExtStatus {
|
impl ProgramExtStatus {
|
||||||
fn new(name: String, icon: Option<concept::IconRc>) -> Self {
|
/// Create a new `ProgramExtStatus`.
|
||||||
|
fn new(name: String, icon: concept::IconRc) -> Self {
|
||||||
Self { name, icon }
|
Self { name, icon }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,8 +749,8 @@ impl ProgramExtStatus {
|
|||||||
/// Get the icon of this program.
|
/// Get the icon of this program.
|
||||||
///
|
///
|
||||||
/// Due to the icon is optional, if there is no icon, return None.
|
/// Due to the icon is optional, if there is no icon, return None.
|
||||||
pub fn get_icon(&self) -> Option<&concept::IconRc> {
|
pub fn get_icon(&self) -> &concept::IconRc {
|
||||||
self.icon.as_ref()
|
&self.icon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use super::{Program, ParseProgramError};
|
|||||||
/// Error occurs when operating with [Schema].
|
/// Error occurs when operating with [Schema].
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
pub enum SchemaError {
|
pub enum SchemaError {
|
||||||
#[error("duplicate key: {0}")]
|
#[error("duplicate key in Schema operation: {0}")]
|
||||||
DuplicateKey(String),
|
DuplicateKey(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ pub struct Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Schema {
|
impl Schema {
|
||||||
|
/// Create an empty `Schema` with all fields set to their defaults.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
identifier: String::new(),
|
identifier: String::new(),
|
||||||
@@ -76,22 +77,40 @@ impl Schema {
|
|||||||
self.path = exe_path.to_string();
|
self.path = exe_path.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the CLSID (Class Identifier) of the application.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently this field is not used by this crate,
|
||||||
|
/// but you still need to fill it with a legal CLSID for future expanding.
|
||||||
pub fn set_clsid(&mut self, clsid: &str) -> () {
|
pub fn set_clsid(&mut self, clsid: &str) -> () {
|
||||||
self.clsid = clsid.to_string();
|
self.clsid = clsid.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the key referencing a named string resource for the application display name.
|
||||||
|
///
|
||||||
|
/// This field is optional and can pass `None` to clear it.
|
||||||
pub fn set_name(&mut self, name: Option<&str>) -> () {
|
pub fn set_name(&mut self, name: Option<&str>) -> () {
|
||||||
self.name = name.map(|n| n.to_string());
|
self.name = name.map(|n| n.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the key referencing a named icon resource for the application default icon.
|
||||||
|
///
|
||||||
|
/// This field is optional and can pass `None` to clear it.
|
||||||
pub fn set_icon(&mut self, icon: Option<&str>) -> () {
|
pub fn set_icon(&mut self, icon: Option<&str>) -> () {
|
||||||
self.icon = icon.map(|i| i.to_string());
|
self.icon = icon.map(|i| i.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the key referencing a named behavior resource for the application default execution behavior.
|
||||||
|
///
|
||||||
|
/// This field is optional and can pass `None` to clear it.
|
||||||
pub fn set_behavior(&mut self, behavior: Option<&str>) -> () {
|
pub fn set_behavior(&mut self, behavior: Option<&str>) -> () {
|
||||||
self.behavior = behavior.map(|b| b.to_string());
|
self.behavior = behavior.map(|b| b.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a named string resource entry.
|
||||||
|
///
|
||||||
|
/// Returns error if the name already exists.
|
||||||
pub fn add_str(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
pub fn add_str(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
||||||
match self.strs.insert(name.to_string(), value.to_string()) {
|
match self.strs.insert(name.to_string(), value.to_string()) {
|
||||||
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
||||||
@@ -99,6 +118,9 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a named icon resource entry.
|
||||||
|
///
|
||||||
|
/// Returns error if the name already exists.
|
||||||
pub fn add_icon(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
pub fn add_icon(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
||||||
match self.icons.insert(name.to_string(), value.to_string()) {
|
match self.icons.insert(name.to_string(), value.to_string()) {
|
||||||
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
||||||
@@ -106,6 +128,9 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a named behavior (command line) entry.
|
||||||
|
///
|
||||||
|
/// Returns error if the name already exists.
|
||||||
pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
pub fn add_behavior(&mut self, name: &str, value: &str) -> Result<(), SchemaError> {
|
||||||
match self.behaviors.insert(name.to_string(), value.to_string()) {
|
match self.behaviors.insert(name.to_string(), value.to_string()) {
|
||||||
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
Some(_) => Err(SchemaError::DuplicateKey(name.to_string())),
|
||||||
@@ -116,6 +141,8 @@ impl Schema {
|
|||||||
/// Add a file extension to the schema.
|
/// Add a file extension to the schema.
|
||||||
///
|
///
|
||||||
/// The parameter `ext` is the file extension without leading dot `.`.
|
/// The parameter `ext` is the file extension without leading dot `.`.
|
||||||
|
/// And the parameter `ext_name`, `ext_icon` and `ext_behavior` are the key
|
||||||
|
/// referencing a named string resource, icon resource and behavior resource respectively.
|
||||||
pub fn add_ext(
|
pub fn add_ext(
|
||||||
&mut self,
|
&mut self,
|
||||||
ext: &str,
|
ext: &str,
|
||||||
@@ -135,42 +162,55 @@ impl Schema {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Schema {
|
impl Schema {
|
||||||
|
/// Get the identifier of the schema.
|
||||||
pub(super) fn get_identifier(&self) -> &str {
|
pub(super) fn get_identifier(&self) -> &str {
|
||||||
&self.identifier
|
&self.identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the absolute path to the executable file.
|
||||||
pub(super) fn get_path(&self) -> &str {
|
pub(super) fn get_path(&self) -> &str {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the CLSID (Class Identifier) of the application.
|
||||||
pub(super) fn get_clsid(&self) -> &str {
|
pub(super) fn get_clsid(&self) -> &str {
|
||||||
&self.clsid
|
&self.clsid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the key referencing the named string resource for the display name.
|
||||||
|
/// Return `None` if user doesn't specify this.
|
||||||
pub(super) fn get_name(&self) -> Option<&str> {
|
pub(super) fn get_name(&self) -> Option<&str> {
|
||||||
self.name.as_ref().map(|v| v.as_str())
|
self.name.as_ref().map(|v| v.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the key referencing the named icon resource for the default icon.
|
||||||
|
/// Return `None` if user doesn't specify this.
|
||||||
pub(super) fn get_icon(&self) -> Option<&str> {
|
pub(super) fn get_icon(&self) -> Option<&str> {
|
||||||
self.icon.as_ref().map(|v| v.as_str())
|
self.icon.as_ref().map(|v| v.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the key referencing the named behavior resource for the default verb.
|
||||||
|
/// Return `None` if user doesn't specify this.
|
||||||
pub(super) fn get_behavior(&self) -> Option<&str> {
|
pub(super) fn get_behavior(&self) -> Option<&str> {
|
||||||
self.icon.as_ref().map(|v| v.as_str())
|
self.behavior.as_ref().map(|v| v.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the string resources map.
|
||||||
pub(super) fn get_strs(&self) -> &HashMap<String, String> {
|
pub(super) fn get_strs(&self) -> &HashMap<String, String> {
|
||||||
&self.strs
|
&self.strs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the icon resources map.
|
||||||
pub(super) fn get_icons(&self) -> &HashMap<String, String> {
|
pub(super) fn get_icons(&self) -> &HashMap<String, String> {
|
||||||
&self.icons
|
&self.icons
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the behavior resources map.
|
||||||
pub(super) fn get_behaviors(&self) -> &HashMap<String, String> {
|
pub(super) fn get_behaviors(&self) -> &HashMap<String, String> {
|
||||||
&self.behaviors
|
&self.behaviors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a reference to the file extensions map.
|
||||||
pub(super) fn get_exts(&self) -> &HashMap<String, SchemaExt> {
|
pub(super) fn get_exts(&self) -> &HashMap<String, SchemaExt> {
|
||||||
&self.exts
|
&self.exts
|
||||||
}
|
}
|
||||||
@@ -189,6 +229,7 @@ pub(crate) struct SchemaExt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SchemaExt {
|
impl SchemaExt {
|
||||||
|
/// Create a new `SchemaExt` with the given name, icon key and behavior key.
|
||||||
fn new(name: &str, icon: &str, behavior: &str) -> Self {
|
fn new(name: &str, icon: &str, behavior: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
@@ -199,14 +240,17 @@ impl SchemaExt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SchemaExt {
|
impl SchemaExt {
|
||||||
|
/// Get the key referencing the named string resource for display name.
|
||||||
pub(super) fn get_name(&self) -> &str {
|
pub(super) fn get_name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the key referencing the named icon resource for file icon.
|
||||||
pub(super) fn get_icon(&self) -> &str {
|
pub(super) fn get_icon(&self) -> &str {
|
||||||
&self.icon
|
&self.icon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the key referencing the named behavior resource.
|
||||||
pub(super) fn get_behavior(&self) -> &str {
|
pub(super) fn get_behavior(&self) -> &str {
|
||||||
&self.behavior
|
&self.behavior
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
//! This crate provide utilities fetching and manilupating Windows file association.
|
//! 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
|
//! 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"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
compile_error!("Crate wfassoc is only supported on Windows.");
|
compile_error!("Crate wfassoc is only supported on Windows.");
|
||||||
|
|||||||
+66
-18
@@ -1,3 +1,5 @@
|
|||||||
|
//! The module containing lowlevel operation involving file association.
|
||||||
|
|
||||||
use crate::win32::{concept, regext, utilities};
|
use crate::win32::{concept, regext, utilities};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -10,27 +12,27 @@ use winreg::enums::{KEY_READ, KEY_WRITE};
|
|||||||
/// Error occurs in this module.
|
/// Error occurs in this module.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("can not perform this operation because lack essential privilege")]
|
#[error("some lowlevel operations require escalating privilege")]
|
||||||
NoPrivilege,
|
NoPrivilege,
|
||||||
#[error("given registry key is inexistant")]
|
#[error("given lowlevel registry key is not presented")]
|
||||||
InexistantKey,
|
InexistantKey,
|
||||||
|
|
||||||
#[error("registry operation error: {0}")]
|
#[error("lowlevel registry operation error: {0}")]
|
||||||
BadRegOp(#[from] std::io::Error),
|
BadRegOp(#[from] std::io::Error),
|
||||||
#[error("{0}")]
|
#[error("unexpected blank registry key in lowlevel registry operation")]
|
||||||
UnexpectedBlankKey(#[from] regext::BlankPathError),
|
UnexpectedBlankKey(#[from] regext::BlankPathError),
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("lowlevel operation error: {0}")]
|
||||||
ExpandEnvVar(#[from] concept::ExpandEnvVarError),
|
ExpandEnvVar(#[from] concept::ExpandEnvVarError),
|
||||||
#[error("{0}")]
|
#[error("lowlevel operation error: {0}")]
|
||||||
LoadIconRc(#[from] concept::LoadIconRcError),
|
LoadIconRc(#[from] concept::LoadIconRcError),
|
||||||
#[error("{0}")]
|
#[error("lowlevel operation error: {0}")]
|
||||||
LoadStrRc(#[from] concept::LoadStrRcError),
|
LoadStrRc(#[from] concept::LoadStrRcError),
|
||||||
#[error("{0}")]
|
#[error("lowlevel operation error: {0}")]
|
||||||
ParseVerb(#[from] concept::ParseVerbError),
|
ParseVerb(#[from] concept::ParseVerbError),
|
||||||
#[error("{0}")]
|
#[error("lowlevel operation error: {0}")]
|
||||||
ParseCmdLine(#[from] concept::ParseCmdLineError),
|
ParseCmdLine(#[from] concept::ParseCmdLineError),
|
||||||
#[error("{0}")]
|
#[error("lowlevel operation error: {0}")]
|
||||||
ParseExt(#[from] concept::ParseExtError),
|
ParseExt(#[from] concept::ParseExtError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +60,7 @@ pub enum Scope {
|
|||||||
pub struct TryFromViewError {}
|
pub struct TryFromViewError {}
|
||||||
|
|
||||||
impl TryFromViewError {
|
impl TryFromViewError {
|
||||||
|
/// Creates a new `TryFromViewError`.
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {}
|
Self {}
|
||||||
}
|
}
|
||||||
@@ -106,9 +109,13 @@ impl From<Scope> for View {
|
|||||||
|
|
||||||
/// The enum representing a losse Programmatic Identifiers (ProgId).
|
/// The enum representing a losse Programmatic Identifiers (ProgId).
|
||||||
///
|
///
|
||||||
/// In real world, not all software developers are willing to following Microsoft suggestions to use ProgId.
|
/// In real world, not all software developers are willing
|
||||||
/// They use string which do not have any regulation as ProgId.
|
/// to following Microsoft suggestions to use ProgId.
|
||||||
|
/// They frequently use string which do not have any regulation as ProgId.
|
||||||
/// This enum is designed for handling this scenario.
|
/// This enum is designed for handling this scenario.
|
||||||
|
///
|
||||||
|
/// You can utilize [FromStr] trait to build this struct.
|
||||||
|
/// Standard ProgId and any casual string are both acceptable.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum LosseProgId {
|
pub enum LosseProgId {
|
||||||
Plain(String),
|
Plain(String),
|
||||||
@@ -151,6 +158,11 @@ impl From<concept::ProgId> for LosseProgId {
|
|||||||
/// In real usage, programmer can use Icon Reference String,
|
/// In real usage, programmer can use Icon Reference String,
|
||||||
/// or a plain string pointing to a icon file as the icon setting value.
|
/// or a plain string pointing to a icon file as the icon setting value.
|
||||||
/// This enum is designed for handling this scenario.
|
/// This enum is designed for handling this scenario.
|
||||||
|
///
|
||||||
|
/// You can utilize [FromStr] trait to build this struct.
|
||||||
|
/// You can pass a standard Icon Reference String or a path to specific icon file.
|
||||||
|
/// However, if you choose the last one, you must make sure
|
||||||
|
/// that your path is must be unquoted, but expand string is allowed.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum IconResVariant {
|
pub enum IconResVariant {
|
||||||
Plain(String),
|
Plain(String),
|
||||||
@@ -158,7 +170,12 @@ pub enum IconResVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IconResVariant {
|
impl IconResVariant {
|
||||||
|
/// Extracts the icon resource with the given size kind.
|
||||||
pub fn extract(&self, kind: concept::IconSizeKind) -> Result<concept::IconRc> {
|
pub fn extract(&self, kind: concept::IconSizeKind) -> Result<concept::IconRc> {
|
||||||
|
// TODO:
|
||||||
|
// Once we implement quote mechanism inside IconRefStr,
|
||||||
|
// there is no need to preserve this unquote code in there.
|
||||||
|
// However the expand string mechanism should be kept.
|
||||||
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) => {
|
IconResVariant::RefStr(v) => {
|
||||||
@@ -214,6 +231,9 @@ impl From<concept::IconRefStr> for IconResVariant {
|
|||||||
/// In real usage, programmer can use String Reference String,
|
/// In real usage, programmer can use String Reference String,
|
||||||
/// or a plain string as the string setting value.
|
/// or a plain string as the string setting value.
|
||||||
/// This enum is designed for handling this scenario.
|
/// This enum is designed for handling this scenario.
|
||||||
|
///
|
||||||
|
/// You can utilize [FromStr] trait to build this struct.
|
||||||
|
/// You can pass a standard String Reference String or a plain string.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub enum StrResVariant {
|
pub enum StrResVariant {
|
||||||
Plain(String),
|
Plain(String),
|
||||||
@@ -221,7 +241,12 @@ pub enum StrResVariant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StrResVariant {
|
impl StrResVariant {
|
||||||
|
/// Resolves this string resource variant into a plain string.
|
||||||
pub fn extract(&self) -> Result<String> {
|
pub fn extract(&self) -> Result<String> {
|
||||||
|
// TODO:
|
||||||
|
// Once we implement quote mechanism inside StrRefStr,
|
||||||
|
// there is no need to preserve this unquote code in there.
|
||||||
|
// However the expand string mechanism should be kept.
|
||||||
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(),
|
||||||
@@ -276,6 +301,12 @@ impl From<concept::StrRefStr> for StrResVariant {
|
|||||||
// region: Shell Verb
|
// region: Shell Verb
|
||||||
|
|
||||||
/// The struct representing a shell verb pair.
|
/// The struct representing a shell verb pair.
|
||||||
|
///
|
||||||
|
/// Shell verb is frequently used in Windows to describe how to use an application.
|
||||||
|
/// It can be used to define the default behavior of application,
|
||||||
|
/// or the way to use this application for opening a specific file.
|
||||||
|
///
|
||||||
|
/// You can use [ShellVerb::new] with given verb and command line infos to build this struct.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ShellVerb {
|
pub struct ShellVerb {
|
||||||
verb: concept::Verb,
|
verb: concept::Verb,
|
||||||
@@ -283,14 +314,17 @@ pub struct ShellVerb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ShellVerb {
|
impl ShellVerb {
|
||||||
|
/// Creates a new `ShellVerb` from a verb and a command.
|
||||||
pub fn new(verb: concept::Verb, command: concept::CmdLine) -> Self {
|
pub fn new(verb: concept::Verb, command: concept::CmdLine) -> Self {
|
||||||
Self { verb, command }
|
Self { verb, command }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the verb of this shell verb pair.
|
||||||
pub fn get_verb(&self) -> &concept::Verb {
|
pub fn get_verb(&self) -> &concept::Verb {
|
||||||
&self.verb
|
&self.verb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the command of this shell verb pair.
|
||||||
pub fn get_command(&self) -> &concept::CmdLine {
|
pub fn get_command(&self) -> &concept::CmdLine {
|
||||||
&self.command
|
&self.command
|
||||||
}
|
}
|
||||||
@@ -304,7 +338,7 @@ impl ShellVerb {
|
|||||||
|
|
||||||
// region: Opened Key
|
// region: Opened Key
|
||||||
|
|
||||||
/// Internal used struct representing the result of opening scope or view.
|
/// Internal used struct representing the result about opening registry key in given scope or view.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct OpenedKey {
|
struct OpenedKey {
|
||||||
/// The parent key of opened key which must be presented.
|
/// The parent key of opened key which must be presented.
|
||||||
@@ -314,6 +348,7 @@ struct OpenedKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OpenedKey {
|
impl OpenedKey {
|
||||||
|
/// Creates a new `OpenedKey` with the given parent key and optional opened key.
|
||||||
fn new(parent_key: RegKey, this_key: Option<RegKey>) -> Self {
|
fn new(parent_key: RegKey, this_key: Option<RegKey>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
parent_key,
|
parent_key,
|
||||||
@@ -326,10 +361,10 @@ impl OpenedKey {
|
|||||||
|
|
||||||
// region: Open Key Territory
|
// region: Open Key Territory
|
||||||
|
|
||||||
/// The territory of opening key.
|
/// Internal used enum representing the territory of opening registry key.
|
||||||
///
|
///
|
||||||
/// Scope and View will finally be converted into this type,
|
/// [Scope] and [View] will finally be converted into this type,
|
||||||
/// and delivered to an uniform function to open key.
|
/// and delivered to an uniform function to open registry key.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
enum OpenKeyTerritory {
|
enum OpenKeyTerritory {
|
||||||
System,
|
System,
|
||||||
@@ -360,7 +395,7 @@ impl From<View> for OpenKeyTerritory {
|
|||||||
|
|
||||||
// region: Open Key Purpose
|
// region: Open Key Purpose
|
||||||
|
|
||||||
/// The purpose of opening this key.
|
/// The purpose of opening given registry key.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
enum OpenKeyPurpose {
|
enum OpenKeyPurpose {
|
||||||
/// Only read something.
|
/// Only read something.
|
||||||
@@ -370,6 +405,7 @@ enum OpenKeyPurpose {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OpenKeyPurpose {
|
impl OpenKeyPurpose {
|
||||||
|
/// Converts this purpose to the corresponding registry permission flags.
|
||||||
fn to_permission(&self) -> u32 {
|
fn to_permission(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
OpenKeyPurpose::Read => PERM_R,
|
OpenKeyPurpose::Read => PERM_R,
|
||||||
@@ -411,8 +447,17 @@ fn check_privilege(territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Resu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove quote pair if possible.
|
// TODO:
|
||||||
|
// This function should be eliminated or moved to another place,
|
||||||
|
// once we finish the quote mechanism in StrRefStr and IconRefStr.
|
||||||
|
|
||||||
|
/// Internal function removing quote pair if possible.
|
||||||
///
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// This function actually is the compromise about the current design of this crate.
|
||||||
|
/// This function will be optimized in future.
|
||||||
|
///
|
||||||
/// In some cases, the path part of [concept::IconRefStr] or [concept::StrRefStr] is quoted by quote.
|
/// In some cases, the path part of [concept::IconRefStr] or [concept::StrRefStr] is quoted by quote.
|
||||||
/// This can no be recognized by Win32 functions.
|
/// This can no be recognized by Win32 functions.
|
||||||
/// So in this case, we should remove this quote pair.
|
/// So in this case, we should remove this quote pair.
|
||||||
@@ -431,6 +476,9 @@ fn strip_quote<'a>(s: &'a str) -> &'a str {
|
|||||||
|
|
||||||
// region: Registry Keys
|
// region: Registry Keys
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Load submodules and re-export them.
|
||||||
|
|
||||||
mod app_path_key;
|
mod app_path_key;
|
||||||
mod applications_key;
|
mod applications_key;
|
||||||
mod ext_key;
|
mod ext_key;
|
||||||
|
|||||||
@@ -5,16 +5,30 @@ use crate::win32::{concept, regext};
|
|||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
use winreg::enums::{HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
/// A registry key wrapper for `Software\Microsoft\Windows\CurrentVersion\App Paths\<APP>`.
|
||||||
|
///
|
||||||
|
/// This key can only be opened under a Scope (not a View), since
|
||||||
|
/// "App Paths" only exists under HKCU and HKLM, not under a merged HKCR view.
|
||||||
|
///
|
||||||
|
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
|
||||||
|
/// check whether this key is in Registry, make sure this key is presneted in Registry,
|
||||||
|
/// and delete self from Registry respectively.
|
||||||
|
///
|
||||||
|
/// And there are some getter and setter in this struct,
|
||||||
|
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
|
||||||
|
/// otherwise these functions will return errors.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct AppPathsKey {
|
pub struct AppPathsKey {
|
||||||
key_name: concept::FileName,
|
key_name: concept::FileName,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppPathsKey {
|
impl AppPathsKey {
|
||||||
|
/// Creates a new `AppPathsKey` with the given file name.
|
||||||
pub fn new(inner: concept::FileName) -> Self {
|
pub fn new(inner: concept::FileName) -> Self {
|
||||||
Self { key_name: inner }
|
Self { key_name: inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inner file name of this application path key.
|
||||||
pub fn inner(&self) -> &concept::FileName {
|
pub fn inner(&self) -> &concept::FileName {
|
||||||
&self.key_name
|
&self.key_name
|
||||||
}
|
}
|
||||||
@@ -23,6 +37,7 @@ impl AppPathsKey {
|
|||||||
impl AppPathsKey {
|
impl AppPathsKey {
|
||||||
const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
|
const APP_PATHS: &str = "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths";
|
||||||
|
|
||||||
|
/// Attempts to open the registry key for this application path with the given territory and purpose.
|
||||||
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
// check privilege
|
// check privilege
|
||||||
check_privilege(territory, purpose)?;
|
check_privilege(territory, purpose)?;
|
||||||
@@ -49,20 +64,24 @@ impl AppPathsKey {
|
|||||||
Ok(OpenedKey::new(app_paths, this_app))
|
Ok(OpenedKey::new(app_paths, this_app))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-only access under the given scope.
|
||||||
fn open_scope_for_read(&self, scope: Scope) -> Result<OpenedKey> {
|
fn open_scope_for_read(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::Read)
|
self.open_key(scope.into(), OpenKeyPurpose::Read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-write access under the given scope.
|
||||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether this application path key exists in the registry under the given scope.
|
||||||
|
/// Return true if it is presented in registry, otherwise false.
|
||||||
pub fn is_exist(&self, scope: Scope) -> Result<bool> {
|
pub fn is_exist(&self, scope: Scope) -> Result<bool> {
|
||||||
let key = self.open_scope_for_read(scope)?.this_key;
|
let key = self.open_scope_for_read(scope)?.this_key;
|
||||||
Ok(key.is_some())
|
Ok(key.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensure this application key is presented in App Paths.
|
/// Ensure this application key is presented in App Paths key.
|
||||||
///
|
///
|
||||||
/// Return true if we newly create this key,
|
/// Return true if we newly create this key,
|
||||||
/// otherwise false indicating there already is an existing key.
|
/// otherwise false indicating there already is an existing key.
|
||||||
@@ -79,66 +98,75 @@ impl AppPathsKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete this application key from App Paths.
|
/// Delete this application key from App Paths key.
|
||||||
///
|
///
|
||||||
/// If there is no such key in App Paths,
|
/// Return true if we successfully delete this key,
|
||||||
/// this function does nothing.
|
/// otherwise false indicating there is no such key (already deleted).
|
||||||
pub fn delete(&mut self, scope: Scope) -> Result<()> {
|
pub fn delete(&mut self, scope: Scope) -> Result<bool> {
|
||||||
let key = self.open_scope_for_write(scope)?;
|
let key = self.open_scope_for_write(scope)?;
|
||||||
key.parent_key
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
.delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?;
|
&key.parent_key,
|
||||||
Ok(())
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YYC MARK:
|
||||||
|
// Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-app-paths-subkey>
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// We only support these keys in there because current interface are enough to use.
|
||||||
|
// We may expand these in future.
|
||||||
|
|
||||||
|
/// Opens the registry key for getter reading, returning an error if the key does not exist.
|
||||||
fn open_scope_for_getter(&self, scope: Scope) -> Result<RegKey> {
|
fn open_scope_for_getter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
self.open_scope_for_read(scope)?
|
self.open_scope_for_read(scope)?
|
||||||
.this_key
|
.this_key
|
||||||
.ok_or(Error::InexistantKey)
|
.ok_or(Error::InexistantKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for setter writing, returning an error if the key does not exist.
|
||||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
self.open_scope_for_write(scope)?
|
self.open_scope_for_write(scope)?
|
||||||
.this_key
|
.this_key
|
||||||
.ok_or(Error::InexistantKey)
|
.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 = "";
|
const NAMEOF_DEFAULT: &str = "";
|
||||||
|
|
||||||
|
/// Gets the default value of this application path key.
|
||||||
///
|
///
|
||||||
///
|
/// The content of this key should point to the fully qualified path of the application.
|
||||||
/// This field point to the fully qualified path to the application.
|
|
||||||
pub fn get_default(&self, scope: Scope) -> Result<String> {
|
pub fn get_default(&self, scope: Scope) -> Result<String> {
|
||||||
let key = self.open_scope_for_getter(scope)?;
|
let key = self.open_scope_for_getter(scope)?;
|
||||||
Ok(key.get_value(Self::NAMEOF_DEFAULT)?)
|
Ok(key.get_value(Self::NAMEOF_DEFAULT)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Sets the default value of this application path key.
|
||||||
///
|
|
||||||
/// This field should be filled with fully qualified path to the application.
|
|
||||||
pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<()> {
|
pub fn set_default(&mut self, scope: Scope, value: &str) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
key.set_value(Self::NAMEOF_DEFAULT, &value)?;
|
key.set_value(Self::NAMEOF_DEFAULT, &value)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// This key may be REG_SZ or REG_EXPAND_SZ.
|
||||||
|
// Currently we see them as one type.
|
||||||
|
// This should be improved in future.
|
||||||
|
|
||||||
const NAMEOF_PATH: &str = "Path";
|
const NAMEOF_PATH: &str = "Path";
|
||||||
|
|
||||||
|
/// Gets the `Path` value of this application path key.
|
||||||
///
|
///
|
||||||
///
|
/// The content of this key will be added to PATH environment variable,
|
||||||
/// This field point to the added path for PATH environment variable.
|
/// to enable that application can find its dependencies conveniently.
|
||||||
/// Usually it is the path to application directory.
|
/// If it contains multiple pathes, semicolon-separated form is required.
|
||||||
|
/// Usually this is the path to application directory.
|
||||||
pub fn get_path(&self, scope: Scope) -> Result<String> {
|
pub fn get_path(&self, scope: Scope) -> Result<String> {
|
||||||
let key = self.open_scope_for_getter(scope)?;
|
let key = self.open_scope_for_getter(scope)?;
|
||||||
Ok(key.get_value(Self::NAMEOF_PATH)?)
|
Ok(key.get_value(Self::NAMEOF_PATH)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Sets the `Path` value of this application path key.
|
||||||
///
|
|
||||||
/// 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<()> {
|
pub fn set_path(&mut self, scope: Scope, value: &str) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
key.set_value(Self::NAMEOF_PATH, &value)?;
|
key.set_value(Self::NAMEOF_PATH, &value)?;
|
||||||
|
|||||||
@@ -6,16 +6,27 @@ use crate::win32::{concept, regext};
|
|||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
/// A registry key wrapper for `Software\Classes\Applications\<APP>`.
|
||||||
|
///
|
||||||
|
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
|
||||||
|
/// check whether this key is in Registry, make sure this key is presneted in Registry,
|
||||||
|
/// and delete self from Registry respectively.
|
||||||
|
///
|
||||||
|
/// And there are some getter and setter in this struct,
|
||||||
|
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
|
||||||
|
/// otherwise these functions will return errors.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ApplicationsKey {
|
pub struct ApplicationsKey {
|
||||||
key_name: concept::FileName,
|
key_name: concept::FileName,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApplicationsKey {
|
impl ApplicationsKey {
|
||||||
|
/// Creates a new `ApplicationsKey` with the given file name.
|
||||||
pub fn new(inner: concept::FileName) -> Self {
|
pub fn new(inner: concept::FileName) -> Self {
|
||||||
Self { key_name: inner }
|
Self { key_name: inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inner file name of this applications key.
|
||||||
pub fn inner(&self) -> &concept::FileName {
|
pub fn inner(&self) -> &concept::FileName {
|
||||||
&self.key_name
|
&self.key_name
|
||||||
}
|
}
|
||||||
@@ -25,6 +36,7 @@ impl ApplicationsKey {
|
|||||||
const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications";
|
const FULL_APPLICATIONS: &str = "Software\\Classes\\Applications";
|
||||||
const PARTIAL_APPLICATIONS: &str = "Applications";
|
const PARTIAL_APPLICATIONS: &str = "Applications";
|
||||||
|
|
||||||
|
/// Attempts to open the registry key for this application with the given territory and purpose.
|
||||||
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
// check privilege
|
// check privilege
|
||||||
check_privilege(territory, purpose)?;
|
check_privilege(territory, purpose)?;
|
||||||
@@ -55,19 +67,27 @@ impl ApplicationsKey {
|
|||||||
Ok(OpenedKey::new(applications, this_app))
|
Ok(OpenedKey::new(applications, this_app))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-only access under the given view.
|
||||||
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
||||||
self.open_key(view.into(), OpenKeyPurpose::Read)
|
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-write access under the given scope.
|
||||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether this applications key exists in the registry under the given view.
|
||||||
|
/// Return true if it is presented in registry, otherwise false.
|
||||||
pub fn is_exist(&self, view: View) -> Result<bool> {
|
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||||
let key = self.open_view_for_read(view)?.this_key;
|
let key = self.open_view_for_read(view)?.this_key;
|
||||||
Ok(key.is_some())
|
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> {
|
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||||
let key = self.open_scope_for_write(scope)?;
|
let key = self.open_scope_for_write(scope)?;
|
||||||
if let None = key.this_key {
|
if let None = key.this_key {
|
||||||
@@ -81,32 +101,54 @@ impl ApplicationsKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, scope: Scope) -> Result<()> {
|
/// 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)?;
|
let key = self.open_scope_for_write(scope)?;
|
||||||
key.parent_key
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
.delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?;
|
&key.parent_key,
|
||||||
Ok(())
|
regext::blank_path_guard(self.key_name.inner())?,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// YYC MARK:
|
// YYC MARK:
|
||||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey
|
// Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/app-registration#using-the-applications-subkey>
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// We only support these keys in there because current interface are enough to use.
|
||||||
|
// We may expand these in future.
|
||||||
|
|
||||||
|
/// Opens the registry key for getter reading, returning an error if the key does not exist.
|
||||||
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
||||||
self.open_view_for_read(view)?
|
self.open_view_for_read(view)?
|
||||||
.this_key
|
.this_key
|
||||||
.ok_or(Error::InexistantKey)
|
.ok_or(Error::InexistantKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for setter writing, returning an error if the key does not exist.
|
||||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
self.open_scope_for_write(scope)?
|
self.open_scope_for_write(scope)?
|
||||||
.this_key
|
.this_key
|
||||||
.ok_or(Error::InexistantKey)
|
.ok_or(Error::InexistantKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Support mutiple ShellVerb in future.
|
||||||
|
|
||||||
const NAMEOF_SHELL_VERB_PART1: &str = "shell";
|
const NAMEOF_SHELL_VERB_PART1: &str = "shell";
|
||||||
const NAMEOF_SHELL_VERB_PART3: &str = "command";
|
const NAMEOF_SHELL_VERB_PART3: &str = "command";
|
||||||
const NAMEOF_SHELL_VERB_PART4: &str = "";
|
const NAMEOF_SHELL_VERB_PART4: &str = "";
|
||||||
|
|
||||||
|
/// Gets the shell verb registered for this application under the given view.
|
||||||
|
/// Return `None` if there is no any shell verb.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently we only support single shell verb.
|
||||||
|
/// So if there is multiple shell verb, this function still return `None`.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
|
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
|
||||||
@@ -157,6 +199,15 @@ impl ApplicationsKey {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the shell verb for this application under the given scope.
|
||||||
|
/// Pass `None` as shell verb to remove the shell verb.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently we only support single shell verb.
|
||||||
|
/// So if there is multiple shell verb, all of them will be deleted at first,
|
||||||
|
/// and then write your given shell verb inside it.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
|
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
@@ -176,7 +227,7 @@ impl ApplicationsKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete shell and its all subkey.
|
// Delete shell and its all subkey.
|
||||||
key.delete_subkey_all(Self::NAMEOF_SHELL_VERB_PART1)?;
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SHELL_VERB_PART1)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +237,10 @@ impl ApplicationsKey {
|
|||||||
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
|
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
|
||||||
const NAMEOF_DEFAULT_ICON_PART2: &str = "";
|
const NAMEOF_DEFAULT_ICON_PART2: &str = "";
|
||||||
|
|
||||||
|
/// Gets the default icon registered for this application under the given view.
|
||||||
|
/// Return `None` if there is no such set default icon.
|
||||||
|
///
|
||||||
|
/// This icon will be used to represent the application instead of the first icon stored in the .exe file.
|
||||||
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
|
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get default icon subkey
|
// Get default icon subkey
|
||||||
@@ -204,6 +259,8 @@ impl ApplicationsKey {
|
|||||||
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
|
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the default icon for this application under the given scope.
|
||||||
|
/// Pass `None` as icon to remove the default icon.
|
||||||
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
|
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
@@ -217,7 +274,7 @@ impl ApplicationsKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete shell and its all subkey.
|
// Delete shell and its all subkey.
|
||||||
key.delete_subkey_all(Self::NAMEOF_DEFAULT_ICON_PART1)?;
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_DEFAULT_ICON_PART1)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,6 +283,10 @@ impl ApplicationsKey {
|
|||||||
|
|
||||||
const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName";
|
const NAMEOF_FRIENDLY_APP_NAME: &str = "FriendlyAppName";
|
||||||
|
|
||||||
|
/// Gets the friendly app name registered for this application under the given view.
|
||||||
|
/// Return `None` if there is no such set friendly app name.
|
||||||
|
///
|
||||||
|
/// This name will be used to display for an application instead of just the version information appearing.
|
||||||
pub fn get_friendly_app_name(&self, view: View) -> Result<Option<StrResVariant>> {
|
pub fn get_friendly_app_name(&self, view: View) -> Result<Option<StrResVariant>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get value of it
|
// Get value of it
|
||||||
@@ -234,6 +295,8 @@ impl ApplicationsKey {
|
|||||||
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the friendly app name for this application under the given scope.
|
||||||
|
/// Pass `None` to remove the friendly app name value.
|
||||||
pub fn set_friendly_app_name(
|
pub fn set_friendly_app_name(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
@@ -248,7 +311,7 @@ impl ApplicationsKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete this key
|
// Delete this key
|
||||||
key.delete_value(Self::NAMEOF_FRIENDLY_APP_NAME)?;
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_FRIENDLY_APP_NAME)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +320,10 @@ impl ApplicationsKey {
|
|||||||
|
|
||||||
const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes";
|
const NAMEOF_SUPPORTED_TYPES: &str = "SupportedTypes";
|
||||||
|
|
||||||
|
/// Gets the supported file types registered for this application under the given view.
|
||||||
|
/// Return `None` if application doesn't set this (because this is suggested but not forced).
|
||||||
|
///
|
||||||
|
/// This is a list defining the file types that the application supports.
|
||||||
pub fn get_supported_types(&self, view: View) -> Result<Option<Vec<concept::Ext>>> {
|
pub fn get_supported_types(&self, view: View) -> Result<Option<Vec<concept::Ext>>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get supported types subkey
|
// Get supported types subkey
|
||||||
@@ -276,6 +343,8 @@ impl ApplicationsKey {
|
|||||||
Ok(Some(exts))
|
Ok(Some(exts))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the supported file types for this application under the given scope.
|
||||||
|
/// Pass `None` to remove this list.
|
||||||
pub fn set_supported_types(
|
pub fn set_supported_types(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
@@ -298,7 +367,7 @@ impl ApplicationsKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete this subkey.
|
// Delete this subkey.
|
||||||
key.delete_subkey_all(Self::NAMEOF_SUPPORTED_TYPES)?;
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SUPPORTED_TYPES)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,6 +376,7 @@ impl ApplicationsKey {
|
|||||||
|
|
||||||
const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith";
|
const NAMEOF_NO_OPEN_WITH: &str = "NoOpenWith";
|
||||||
|
|
||||||
|
/// Checks whether the "NoOpenWith" flag is set for this application under the given view.
|
||||||
pub fn get_no_open_with(&self, view: View) -> Result<bool> {
|
pub fn get_no_open_with(&self, view: View) -> Result<bool> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
match regext::try_get_value::<String, _>(&key, Self::NAMEOF_NO_OPEN_WITH)? {
|
match regext::try_get_value::<String, _>(&key, Self::NAMEOF_NO_OPEN_WITH)? {
|
||||||
@@ -315,12 +385,17 @@ impl ApplicationsKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets or clears the "NoOpenWith" flag for this application under the given scope.
|
||||||
|
///
|
||||||
|
/// Microsoft document said that this key indicates that no application is specified for opening this file type.
|
||||||
|
/// But I totally doesn't understand this. However, most of application set this.
|
||||||
|
/// So let we set it together.
|
||||||
pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<()> {
|
pub fn set_no_open_with(&mut self, scope: Scope, flag: bool) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
if flag {
|
if flag {
|
||||||
key.set_value(Self::NAMEOF_NO_OPEN_WITH, &"")?;
|
key.set_value(Self::NAMEOF_NO_OPEN_WITH, &"")?;
|
||||||
} else {
|
} else {
|
||||||
key.delete_value(Self::NAMEOF_NO_OPEN_WITH)?;
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_NO_OPEN_WITH)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,27 @@ use crate::win32::{concept, regext};
|
|||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
/// A registry key wrapper for `Software\Classes\<EXTENSION>`.
|
||||||
|
///
|
||||||
|
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
|
||||||
|
/// check whether this key is in Registry, make sure this key is presneted in Registry,
|
||||||
|
/// and delete self from Registry respectively.
|
||||||
|
///
|
||||||
|
/// And there are some getter and setter in this struct,
|
||||||
|
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
|
||||||
|
/// otherwise these functions will return errors.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ExtKey {
|
pub struct ExtKey {
|
||||||
ext: concept::Ext,
|
ext: concept::Ext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtKey {
|
impl ExtKey {
|
||||||
|
/// Creates a new `ExtKey` with the given file extension.
|
||||||
pub fn new(inner: concept::Ext) -> Self {
|
pub fn new(inner: concept::Ext) -> Self {
|
||||||
Self { ext: inner }
|
Self { ext: inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inner file extension of this extension key.
|
||||||
pub fn inner(&self) -> &concept::Ext {
|
pub fn inner(&self) -> &concept::Ext {
|
||||||
&self.ext
|
&self.ext
|
||||||
}
|
}
|
||||||
@@ -25,6 +36,7 @@ impl ExtKey {
|
|||||||
const FULL_CLASSES: &str = "Software\\Classes";
|
const FULL_CLASSES: &str = "Software\\Classes";
|
||||||
const PARTIAL_CLASSES: &str = "";
|
const PARTIAL_CLASSES: &str = "";
|
||||||
|
|
||||||
|
/// Attempts to open the registry key for this file extension with the given territory and purpose.
|
||||||
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
// check privilege
|
// check privilege
|
||||||
check_privilege(territory, purpose)?;
|
check_privilege(territory, purpose)?;
|
||||||
@@ -53,19 +65,27 @@ impl ExtKey {
|
|||||||
Ok(OpenedKey::new(classes, this_ext))
|
Ok(OpenedKey::new(classes, this_ext))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-only access under the given view.
|
||||||
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
||||||
self.open_key(view.into(), OpenKeyPurpose::Read)
|
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-write access under the given scope.
|
||||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether this file extension key exists in the registry under the given view.
|
||||||
|
/// Return true if it is presented in registry, otherwise false.
|
||||||
pub fn is_exist(&self, view: View) -> Result<bool> {
|
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||||
let key = self.open_view_for_read(view)?.this_key;
|
let key = self.open_view_for_read(view)?.this_key;
|
||||||
Ok(key.is_some())
|
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> {
|
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||||
let key = self.open_scope_for_write(scope)?;
|
let key = self.open_scope_for_write(scope)?;
|
||||||
if let None = key.this_key {
|
if let None = key.this_key {
|
||||||
@@ -79,28 +99,34 @@ impl ExtKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, scope: Scope) -> Result<()> {
|
/// 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)?;
|
let key = self.open_scope_for_write(scope)?;
|
||||||
key.parent_key
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
.delete_subkey_all(regext::blank_path_guard(self.ext.dotted_inner())?)?;
|
&key.parent_key,
|
||||||
Ok(())
|
regext::blank_path_guard(self.ext.dotted_inner())?,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// YYC MARK:
|
// YYC MARK:
|
||||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes
|
// Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#setting-optional-subkeys-and-file-type-extension-attributes>
|
||||||
|
// And we explicitly do not support "OpenWithList", because it is obsolete in modern Windows.
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// We do not support "Content Type" and "PerceivedType"
|
// We only support these keys in there because current interface are enough to use.
|
||||||
// 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.
|
// We may expand these in future.
|
||||||
|
|
||||||
|
/// Opens the registry key for getter reading, returning an error if the key does not exist.
|
||||||
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
||||||
self.open_view_for_read(view)?
|
self.open_view_for_read(view)?
|
||||||
.this_key
|
.this_key
|
||||||
.ok_or(Error::InexistantKey)
|
.ok_or(Error::InexistantKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for setter writing, returning an error if the key does not exist.
|
||||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
self.open_scope_for_write(scope)?
|
self.open_scope_for_write(scope)?
|
||||||
.this_key
|
.this_key
|
||||||
@@ -109,6 +135,10 @@ impl ExtKey {
|
|||||||
|
|
||||||
const NAMEOF_DEFAULT: &str = "";
|
const NAMEOF_DEFAULT: &str = "";
|
||||||
|
|
||||||
|
/// Gets the default value of this file extension under the given view.
|
||||||
|
/// Return `None` if there is no association for this file extension.
|
||||||
|
///
|
||||||
|
/// The content of this key is this file extension associated ProgId.
|
||||||
pub fn get_default(&self, view: View) -> Result<Option<LosseProgId>> {
|
pub fn get_default(&self, view: View) -> Result<Option<LosseProgId>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get value of it
|
// Get value of it
|
||||||
@@ -117,6 +147,8 @@ impl ExtKey {
|
|||||||
Ok(value.map(|v| LosseProgId::from(v.as_str())))
|
Ok(value.map(|v| LosseProgId::from(v.as_str())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the default value (ProgId association) for this file extension under the given scope.
|
||||||
|
/// Pass `None` to remove the association.
|
||||||
pub fn set_default(&mut self, scope: Scope, pid: Option<&LosseProgId>) -> Result<()> {
|
pub fn set_default(&mut self, scope: Scope, pid: Option<&LosseProgId>) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
@@ -127,7 +159,7 @@ impl ExtKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete this key
|
// Delete this key
|
||||||
key.delete_value(Self::NAMEOF_DEFAULT)?;
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_DEFAULT)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +168,11 @@ impl ExtKey {
|
|||||||
|
|
||||||
const NAMEOF_OPEN_WITH_PROGIDS: &str = "OpenWithProgIds";
|
const NAMEOF_OPEN_WITH_PROGIDS: &str = "OpenWithProgIds";
|
||||||
|
|
||||||
|
/// Gets the list of ProgIds registered in the "OpenWithProgIds" subkey under the given view.
|
||||||
|
/// Return `None` is there is no this subkey.
|
||||||
|
///
|
||||||
|
/// This subkey contains a list of alternate ProgId for this file type.
|
||||||
|
/// In Windows Explorer, these ProgId should appear in the Open with menu in right click menu of this file extension.
|
||||||
pub fn get_open_with_progids(&self, view: View) -> Result<Option<Vec<LosseProgId>>> {
|
pub fn get_open_with_progids(&self, view: View) -> Result<Option<Vec<LosseProgId>>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get OpenWithProgIds subkey
|
// Get OpenWithProgIds subkey
|
||||||
@@ -156,7 +193,7 @@ impl ExtKey {
|
|||||||
Ok(Some(progids))
|
Ok(Some(progids))
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Checks whether the given ProgId is in the "OpenWithProgIds" subkey under the given view.
|
||||||
///
|
///
|
||||||
/// If there is no "OpenWithProgIds" subkey, this function return false.
|
/// If there is no "OpenWithProgIds" subkey, this function return false.
|
||||||
pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result<bool> {
|
pub fn is_in_open_with_progids(&self, view: View, pid: &LosseProgId) -> Result<bool> {
|
||||||
@@ -176,7 +213,7 @@ impl ExtKey {
|
|||||||
.is_some())
|
.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Adds the given ProgId into the "OpenWithProgIds" subkey under the given scope.
|
||||||
///
|
///
|
||||||
/// If there is no "OpenWithProgIds" subkey, this function will create it first,
|
/// If there is no "OpenWithProgIds" subkey, this function will create it first,
|
||||||
/// then add your given ProgId into it.
|
/// then add your given ProgId into it.
|
||||||
@@ -190,9 +227,9 @@ impl ExtKey {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the given ProgId from the "OpenWithProgIds" subkey under the given scope.
|
||||||
///
|
///
|
||||||
///
|
/// If there is no "OpenWithProgIds" subkey, this function does nothing.
|
||||||
/// If there is no "OpenWithProgIds" subkey, this function do nothing.
|
|
||||||
pub fn remove_from_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> {
|
pub fn remove_from_open_with_progids(&mut self, scope: Scope, pid: &LosseProgId) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
// Try get subkey
|
// Try get subkey
|
||||||
@@ -205,7 +242,10 @@ impl ExtKey {
|
|||||||
None => return Ok(()),
|
None => return Ok(()),
|
||||||
};
|
};
|
||||||
// Remove given key
|
// Remove given key
|
||||||
open_with_progids_key.delete_value(pid.to_string())?;
|
regext::arbitrarily_delete_value(
|
||||||
|
&open_with_progids_key,
|
||||||
|
regext::blank_path_guard(pid.to_string())?,
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,27 @@ use crate::win32::regext;
|
|||||||
use winreg::RegKey;
|
use winreg::RegKey;
|
||||||
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
use winreg::enums::{HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
|
||||||
|
|
||||||
|
/// A registry key wrapper for `Software\Classes\<PROGID>`.
|
||||||
|
///
|
||||||
|
/// This struct provides [Self::is_exist], [Self::ensure] and [Self::delete] to
|
||||||
|
/// check whether this key is in Registry, make sure this key is presneted in Registry,
|
||||||
|
/// and delete self from Registry respectively.
|
||||||
|
///
|
||||||
|
/// And there are some getter and setter in this struct,
|
||||||
|
/// before calling them, you must use [Self::ensure] make sure that this key is presented in Registry,
|
||||||
|
/// otherwise these functions will return errors.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ProgIdKey {
|
pub struct ProgIdKey {
|
||||||
progid: LosseProgId,
|
progid: LosseProgId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProgIdKey {
|
impl ProgIdKey {
|
||||||
|
/// Creates a new `ProgIdKey` with the given ProgId.
|
||||||
pub fn new(inner: LosseProgId) -> Self {
|
pub fn new(inner: LosseProgId) -> Self {
|
||||||
Self { progid: inner }
|
Self { progid: inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the inner ProgId of this ProgId key.
|
||||||
pub fn inner(&self) -> &LosseProgId {
|
pub fn inner(&self) -> &LosseProgId {
|
||||||
&self.progid
|
&self.progid
|
||||||
}
|
}
|
||||||
@@ -25,6 +36,7 @@ impl ProgIdKey {
|
|||||||
const FULL_CLASSES: &str = "Software\\Classes";
|
const FULL_CLASSES: &str = "Software\\Classes";
|
||||||
const PARTIAL_CLASSES: &str = "";
|
const PARTIAL_CLASSES: &str = "";
|
||||||
|
|
||||||
|
/// Attempts to open the registry key for this ProgId with the given territory and purpose.
|
||||||
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
fn open_key(&self, territory: OpenKeyTerritory, purpose: OpenKeyPurpose) -> Result<OpenedKey> {
|
||||||
// check privilege
|
// check privilege
|
||||||
check_privilege(territory, purpose)?;
|
check_privilege(territory, purpose)?;
|
||||||
@@ -53,19 +65,27 @@ impl ProgIdKey {
|
|||||||
Ok(OpenedKey::new(classes, this_progid))
|
Ok(OpenedKey::new(classes, this_progid))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-only access under the given view.
|
||||||
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
fn open_view_for_read(&self, view: View) -> Result<OpenedKey> {
|
||||||
self.open_key(view.into(), OpenKeyPurpose::Read)
|
self.open_key(view.into(), OpenKeyPurpose::Read)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for read-write access under the given scope.
|
||||||
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
fn open_scope_for_write(&self, scope: Scope) -> Result<OpenedKey> {
|
||||||
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
self.open_key(scope.into(), OpenKeyPurpose::ReadWrite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether this ProgId key exists in the registry under the given view.
|
||||||
|
/// Return true if it is presented in registry, otherwise false.
|
||||||
pub fn is_exist(&self, view: View) -> Result<bool> {
|
pub fn is_exist(&self, view: View) -> Result<bool> {
|
||||||
let key = self.open_view_for_read(view)?.this_key;
|
let key = self.open_view_for_read(view)?.this_key;
|
||||||
Ok(key.is_some())
|
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> {
|
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
|
||||||
let key = self.open_scope_for_write(scope)?;
|
let key = self.open_scope_for_write(scope)?;
|
||||||
if let None = key.this_key {
|
if let None = key.this_key {
|
||||||
@@ -79,27 +99,33 @@ impl ProgIdKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, scope: Scope) -> Result<()> {
|
/// 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)?;
|
let key = self.open_scope_for_write(scope)?;
|
||||||
key.parent_key
|
Ok(regext::arbitrarily_delete_subkey_all(
|
||||||
.delete_subkey_all(regext::blank_path_guard(self.progid.to_string())?)?;
|
&key.parent_key,
|
||||||
Ok(())
|
regext::blank_path_guard(self.progid.to_string())?,
|
||||||
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
// YYC MARK:
|
// YYC MARK:
|
||||||
// Reference: https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations
|
// Reference: <https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids#programmatic-identifier-elements-used-by-file-associations>
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// Currently we only support (Default), FriendlyTypeName and DefaultIcon
|
// We only support these keys in there because current interface are enough to use.
|
||||||
// to just cover the basic usage.
|
|
||||||
// We may expand these in future.
|
// We may expand these in future.
|
||||||
|
|
||||||
|
/// Opens the registry key for getter reading, returning an error if the key does not exist.
|
||||||
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
fn open_view_for_getter(&self, view: View) -> Result<RegKey> {
|
||||||
self.open_view_for_read(view)?
|
self.open_view_for_read(view)?
|
||||||
.this_key
|
.this_key
|
||||||
.ok_or(Error::InexistantKey)
|
.ok_or(Error::InexistantKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opens the registry key for setter writing, returning an error if the key does not exist.
|
||||||
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
fn open_scope_for_setter(&self, scope: Scope) -> Result<RegKey> {
|
||||||
self.open_scope_for_write(scope)?
|
self.open_scope_for_write(scope)?
|
||||||
.this_key
|
.this_key
|
||||||
@@ -108,6 +134,11 @@ impl ProgIdKey {
|
|||||||
|
|
||||||
const NAMEOF_DEFAULT: &str = "";
|
const NAMEOF_DEFAULT: &str = "";
|
||||||
|
|
||||||
|
/// Gets the default value of this ProgId under the given view.
|
||||||
|
///
|
||||||
|
/// The content of this key is the legacy way to introduce the friendly name of application.
|
||||||
|
/// If you are fetching friendly name, please choose "FriendlyTypeName" at first, then fallback to this.
|
||||||
|
/// If you are setting friendly name, please set this together.
|
||||||
pub fn get_default(&self, view: View) -> Result<Option<StrResVariant>> {
|
pub fn get_default(&self, view: View) -> Result<Option<StrResVariant>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get value of it
|
// Get value of it
|
||||||
@@ -116,9 +147,7 @@ impl ProgIdKey {
|
|||||||
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Sets the default value (friendly name) for this ProgId under the given scope.
|
||||||
///
|
|
||||||
/// The legacy way to set friendly name for this ProgId.
|
|
||||||
pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<()> {
|
pub fn set_default(&mut self, scope: Scope, name: Option<&StrResVariant>) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
@@ -129,17 +158,28 @@ impl ProgIdKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete this key
|
// Delete this key
|
||||||
key.delete_value(Self::NAMEOF_DEFAULT)?;
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_DEFAULT)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Support mutiple ShellVerb in future.
|
||||||
|
|
||||||
const NAMEOF_SHELL_VERB_PART1: &str = "shell";
|
const NAMEOF_SHELL_VERB_PART1: &str = "shell";
|
||||||
const NAMEOF_SHELL_VERB_PART3: &str = "command";
|
const NAMEOF_SHELL_VERB_PART3: &str = "command";
|
||||||
const NAMEOF_SHELL_VERB_PART4: &str = "";
|
const NAMEOF_SHELL_VERB_PART4: &str = "";
|
||||||
|
|
||||||
|
/// Gets the shell verb registered for this ProgId under the given view.
|
||||||
|
/// Return `None` if there is no any shell verb.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently we only support single shell verb.
|
||||||
|
/// So if there is multiple shell verb, this function still return `None`.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
|
pub fn get_shell_verb(&self, view: View) -> Result<Option<ShellVerb>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
|
|
||||||
@@ -190,6 +230,15 @@ impl ProgIdKey {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the shell verb for this ProgId under the given scope.
|
||||||
|
/// Pass `None` as shell verb to remove the shell verb.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently we only support single shell verb.
|
||||||
|
/// So if there is multiple shell verb, all of them will be deleted at first,
|
||||||
|
/// and then write your given shell verb inside it.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
|
pub fn set_shell_verb(&mut self, scope: Scope, sv: Option<&ShellVerb>) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
@@ -209,15 +258,24 @@ impl ProgIdKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete shell and its all subkey.
|
// Delete shell and its all subkey.
|
||||||
key.delete_subkey_all(Self::NAMEOF_SHELL_VERB_PART1)?;
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SHELL_VERB_PART1)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// This key may be REG_SZ or REG_EXPAND_SZ.
|
||||||
|
// Currently we see them as one type.
|
||||||
|
// This should be improved in future.
|
||||||
|
|
||||||
const NAMEOF_FRIENDLY_TYPE_NAME: &str = "FriendlyTypeName";
|
const NAMEOF_FRIENDLY_TYPE_NAME: &str = "FriendlyTypeName";
|
||||||
|
|
||||||
|
/// Gets the friendly type name registered for this ProgId under the given view.
|
||||||
|
/// Return `None` if there is no such key.
|
||||||
|
///
|
||||||
|
/// The content of this key is the file type name which is suitable to display to the user.
|
||||||
pub fn get_friendly_type_name(&self, view: View) -> Result<Option<StrResVariant>> {
|
pub fn get_friendly_type_name(&self, view: View) -> Result<Option<StrResVariant>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get value of it
|
// Get value of it
|
||||||
@@ -226,9 +284,8 @@ impl ProgIdKey {
|
|||||||
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
Ok(value.map(|v| StrResVariant::from(v.as_str())))
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
/// Sets the friendly type name for this ProgId under the given scope.
|
||||||
///
|
/// Pass `None` to remove friendly type name.
|
||||||
/// Set this entry to a friendly name for the ProgID.
|
|
||||||
pub fn set_friendly_type_name(
|
pub fn set_friendly_type_name(
|
||||||
&mut self,
|
&mut self,
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
@@ -243,16 +300,25 @@ impl ProgIdKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete this key
|
// Delete this key
|
||||||
key.delete_value(Self::NAMEOF_FRIENDLY_TYPE_NAME)?;
|
regext::arbitrarily_delete_value(&key, Self::NAMEOF_FRIENDLY_TYPE_NAME)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// This key may be REG_SZ or REG_EXPAND_SZ.
|
||||||
|
// Currently we see them as one type.
|
||||||
|
// This should be improved in future.
|
||||||
|
|
||||||
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
|
const NAMEOF_DEFAULT_ICON_PART1: &str = "DefaultIcon";
|
||||||
const NAMEOF_DEFAULT_ICON_PART2: &str = "";
|
const NAMEOF_DEFAULT_ICON_PART2: &str = "";
|
||||||
|
|
||||||
|
/// Gets the default icon registered for this ProgId under the given view.
|
||||||
|
/// Return `None` if there is no default icon.
|
||||||
|
///
|
||||||
|
/// The content of this key will be displayed for file types associated with this ProgId.
|
||||||
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
|
pub fn get_default_icon(&self, view: View) -> Result<Option<IconResVariant>> {
|
||||||
let key = self.open_view_for_getter(view)?;
|
let key = self.open_view_for_getter(view)?;
|
||||||
// Get default icon subkey
|
// Get default icon subkey
|
||||||
@@ -271,6 +337,11 @@ impl ProgIdKey {
|
|||||||
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
|
Ok(default_icon_default_value.map(|v| IconResVariant::from(v.as_str())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the default icon for this ProgId under the given scope.
|
||||||
|
/// Pass `None` to remove the default icon subkey.
|
||||||
|
///
|
||||||
|
/// Although this function give the ability that do not set this key,
|
||||||
|
/// however, I strongly suggest that set this to let file associated with this ProgId have an icon.
|
||||||
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
|
pub fn set_default_icon(&mut self, scope: Scope, icon: Option<&IconResVariant>) -> Result<()> {
|
||||||
let key = self.open_scope_for_setter(scope)?;
|
let key = self.open_scope_for_setter(scope)?;
|
||||||
|
|
||||||
@@ -284,7 +355,7 @@ impl ProgIdKey {
|
|||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// Delete shell and its all subkey.
|
// Delete shell and its all subkey.
|
||||||
key.delete_subkey_all(Self::NAMEOF_DEFAULT_ICON_PART1)?;
|
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_DEFAULT_ICON_PART1)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ use std::iter::FusedIterator;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use thiserror::Error as TeError;
|
use thiserror::Error as TeError;
|
||||||
|
|
||||||
// region: OS String Related
|
// region: OS String Casting
|
||||||
|
|
||||||
/// The error occurs when casting `OsStr` into `str`.
|
/// The error occurs when casting [OsStr] into [str].
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("fail to cast OS string into string")]
|
#[error("fail to cast OS string into Rust string")]
|
||||||
pub struct CastOsStrError {}
|
pub struct CastOsStrError {}
|
||||||
|
|
||||||
impl CastOsStrError {
|
impl CastOsStrError {
|
||||||
@@ -32,6 +32,8 @@ pub fn osstr_to_str(osstr: &OsStr) -> Result<&str, CastOsStrError> {
|
|||||||
|
|
||||||
// region: Capitalize First ASCII Letter
|
// region: Capitalize First ASCII Letter
|
||||||
|
|
||||||
|
/// The struct transforming accept an iterator and perform as an iterator
|
||||||
|
/// whose transform the first character of accepted iterator into capital form (ASCII char only).
|
||||||
struct CapitalizeFirstAscii<T>
|
struct CapitalizeFirstAscii<T>
|
||||||
where
|
where
|
||||||
T: Iterator<Item = char>,
|
T: Iterator<Item = char>,
|
||||||
@@ -72,6 +74,7 @@ where
|
|||||||
|
|
||||||
impl<T> FusedIterator for CapitalizeFirstAscii<T> where T: Iterator<Item = char> + FusedIterator {}
|
impl<T> FusedIterator for CapitalizeFirstAscii<T> where T: Iterator<Item = char> + FusedIterator {}
|
||||||
|
|
||||||
|
/// Accept an string slice, transform its first char (ASCII only) as capital form and output.
|
||||||
pub fn capitalize_first_ascii(s: &str) -> String {
|
pub fn capitalize_first_ascii(s: &str) -> String {
|
||||||
CapitalizeFirstAscii::new(s.chars()).collect()
|
CapitalizeFirstAscii::new(s.chars()).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
//! The module containing Win32 stuff related to file association operation,
|
||||||
|
//! including Win32 concept, Registry operation helper and etc.
|
||||||
|
|
||||||
pub mod concept;
|
pub mod concept;
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub mod regext;
|
pub mod regext;
|
||||||
+215
-28
@@ -15,7 +15,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::HICON;
|
|||||||
|
|
||||||
/// The error occurs when constructing Ext with bad body.
|
/// The error occurs when constructing Ext with bad body.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given file extension body \"{inner}\" is invalid")]
|
#[error(r#"given file extension body "{inner}" is invalid"#)]
|
||||||
pub struct BadExtBodyError {
|
pub struct BadExtBodyError {
|
||||||
/// The clone of string which is not a valid file extension body.
|
/// The clone of string which is not a valid file extension body.
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -32,6 +32,12 @@ impl BadExtBodyError {
|
|||||||
|
|
||||||
/// The struct representing an file extension which must start with dot (`.`)
|
/// The struct representing an file extension which must start with dot (`.`)
|
||||||
/// and followed by at least one arbitrary characters.
|
/// and followed by at least one arbitrary characters.
|
||||||
|
///
|
||||||
|
/// If you having a file extension expressed in string form with leading dot,
|
||||||
|
/// please utilize [FromStr] trait to build this struct.
|
||||||
|
/// Otherwise, use [Ext::new] with file extension without leading dot.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always outputs file extension with leading dot.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Ext {
|
pub struct Ext {
|
||||||
/// The body of file extension (excluding dot).
|
/// The body of file extension (excluding dot).
|
||||||
@@ -42,6 +48,8 @@ impl Ext {
|
|||||||
/// Create an new file extension.
|
/// Create an new file extension.
|
||||||
///
|
///
|
||||||
/// `body` is the body of file extension (excluding dot, such as `jpg`).
|
/// `body` is the body of file extension (excluding dot, such as `jpg`).
|
||||||
|
/// Empty body is not allowed.
|
||||||
|
///
|
||||||
/// If you want to create this struct with ordinary extension string like `.jpg`,
|
/// If you want to create this struct with ordinary extension string like `.jpg`,
|
||||||
/// please use `Ext::from_str()` or `parse::<Ext>()` instead.
|
/// please use `Ext::from_str()` or `parse::<Ext>()` instead.
|
||||||
pub fn new(body: &str) -> Result<Self, BadExtBodyError> {
|
pub fn new(body: &str) -> Result<Self, BadExtBodyError> {
|
||||||
@@ -56,11 +64,17 @@ impl Ext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the body part of file extension (excluding dot)
|
/// Get the body part of file extension (excluding dot)
|
||||||
|
///
|
||||||
|
/// This struct promise that there is no possibility that return value is blank
|
||||||
|
/// (i.e. the body of file extension is empty).
|
||||||
pub fn inner(&self) -> &str {
|
pub fn inner(&self) -> &str {
|
||||||
&self.body
|
&self.body
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the body part of file extension (with leading dot)
|
/// Get the body part of file extension (with leading dot)
|
||||||
|
///
|
||||||
|
/// This struct promise that there is no possibility that return value is dot only
|
||||||
|
/// (i.e. the body of file extension is empty).
|
||||||
pub fn dotted_inner(&self) -> String {
|
pub fn dotted_inner(&self) -> String {
|
||||||
// Reuse Display trait result
|
// Reuse Display trait result
|
||||||
self.to_string()
|
self.to_string()
|
||||||
@@ -69,7 +83,7 @@ impl Ext {
|
|||||||
|
|
||||||
/// The error occurs when try parsing string into FileExt.
|
/// The error occurs when try parsing string into FileExt.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given file extension name \"{inner}\" is invalid")]
|
#[error(r#"given file extension name "{inner}" is invalid"#)]
|
||||||
pub struct ParseExtError {
|
pub struct ParseExtError {
|
||||||
/// The clone of string which is not a valid file extension.
|
/// The clone of string which is not a valid file extension.
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -110,7 +124,7 @@ impl FromStr for Ext {
|
|||||||
|
|
||||||
/// The error occurs when constructing ProgId.
|
/// The error occurs when constructing ProgId.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given ProgId part \"{inner}\" is invalid")]
|
#[error(r#"given ProgId part "{inner}" is invalid"#)]
|
||||||
pub struct BadProgIdPartError {
|
pub struct BadProgIdPartError {
|
||||||
/// The clone of string which is not a valid ProgId part.
|
/// The clone of string which is not a valid ProgId part.
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -127,16 +141,21 @@ impl BadProgIdPartError {
|
|||||||
|
|
||||||
/// The ProgId exactly follows Microsoft suggested
|
/// The ProgId exactly follows Microsoft suggested
|
||||||
/// `[Vendor or Application].[Component].[Version]` format.
|
/// `[Vendor or Application].[Component].[Version]` format.
|
||||||
|
/// And additionally, `[Version]` part is optional.
|
||||||
///
|
///
|
||||||
/// Additionally, `[Version]` part is optional.
|
/// In reality world, most of applications do no follow this standard.
|
||||||
///
|
/// However, this scenario is not convered by this struct in there.
|
||||||
/// However, most of applications do no follow this standard,
|
|
||||||
/// this scenario is not convered by this struct in there.
|
|
||||||
/// It should be done by other structs in other places.
|
/// It should be done by other structs in other places.
|
||||||
///
|
///
|
||||||
|
/// If you have a ProgId expressed in string form, please utilize [FromStr] trait to parse it.
|
||||||
|
/// Or use [ProgId::new] with each parts to construct this struct.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// outputs legal ProgId in string form.
|
||||||
|
///
|
||||||
/// Reference:
|
/// Reference:
|
||||||
/// - https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids
|
/// - <https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids>
|
||||||
/// - https://learn.microsoft.com/en-us/windows/win32/com/-progid--key
|
/// - <https://learn.microsoft.com/en-us/windows/win32/com/-progid--key>
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ProgId {
|
pub struct ProgId {
|
||||||
/// The vendor part of ProgId.
|
/// The vendor part of ProgId.
|
||||||
@@ -149,6 +168,9 @@ pub struct ProgId {
|
|||||||
|
|
||||||
impl ProgId {
|
impl ProgId {
|
||||||
/// Create a new ProgId with given parts.
|
/// Create a new ProgId with given parts.
|
||||||
|
///
|
||||||
|
/// Empty string or string with embedded dot is not allowed
|
||||||
|
/// for vendor and component parts.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
vendor: &str,
|
vendor: &str,
|
||||||
component: &str,
|
component: &str,
|
||||||
@@ -186,7 +208,7 @@ impl ProgId {
|
|||||||
|
|
||||||
/// The error occurs when parsing ProgId.
|
/// The error occurs when parsing ProgId.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given ProgId \"{inner}\" is invalid")]
|
#[error(r#"given ProgId "{inner}" is invalid"#)]
|
||||||
pub struct ParseProgIdError {
|
pub struct ParseProgIdError {
|
||||||
/// The clone of string which is not a valid ProgId.
|
/// The clone of string which is not a valid ProgId.
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -245,6 +267,14 @@ impl FromStr for ProgId {
|
|||||||
/// `{26EE0668-A00A-44D7-9371-BEB064C98683}` (case insensitive).
|
/// `{26EE0668-A00A-44D7-9371-BEB064C98683}` (case insensitive).
|
||||||
///
|
///
|
||||||
/// Please note that the curly brace is the essential part of CLSID.
|
/// Please note that the curly brace is the essential part of CLSID.
|
||||||
|
///
|
||||||
|
/// If you have a CLSID expressed in string form,
|
||||||
|
/// please utilize [FromStr] trait to parse it.
|
||||||
|
/// You also can utilize [Clsid::new] with [uuid] crate to create a custom CLSID,
|
||||||
|
/// or directly use [Clsid::with_random] to create a random new one.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// output CLSID in lower-case with curly brace.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Clsid {
|
pub struct Clsid {
|
||||||
inner: Uuid,
|
inner: Uuid,
|
||||||
@@ -252,7 +282,7 @@ pub struct Clsid {
|
|||||||
|
|
||||||
impl Clsid {
|
impl Clsid {
|
||||||
/// Create new CLSID from underlying UUID.
|
/// Create new CLSID from underlying UUID.
|
||||||
fn new(uuid: &Uuid) -> Self {
|
pub fn new(uuid: &Uuid) -> Self {
|
||||||
Self { inner: *uuid }
|
Self { inner: *uuid }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +294,7 @@ impl Clsid {
|
|||||||
|
|
||||||
/// The error occurs when parsing CLSID.
|
/// The error occurs when parsing CLSID.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given string \"{inner}\" is invalid for uuid")]
|
#[error(r#"given string "{inner}" is invalid for CLSID"#)]
|
||||||
pub struct ParseClsidError {
|
pub struct ParseClsidError {
|
||||||
/// The clone of string which is not a valid CLSID.
|
/// The clone of string which is not a valid CLSID.
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -301,7 +331,7 @@ impl Display for Clsid {
|
|||||||
|
|
||||||
/// Error occurs when given string is not a valid Icon Reference String.
|
/// Error occurs when given string is not a valid Icon Reference String.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given string \"{inner}\" is not a valid Icon Reference String")]
|
#[error(r#"given string "{inner}" is not a valid Icon Reference String"#)]
|
||||||
pub struct ParseIconRefStrError {
|
pub struct ParseIconRefStrError {
|
||||||
/// The clone of string which is not a valid Icon Reference String
|
/// The clone of string which is not a valid Icon Reference String
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -316,11 +346,22 @@ impl ParseIconRefStrError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// IconRefStr should process the quote issue for path part in itself,
|
||||||
|
// rather than fetching its path part and resolve quote issue in another place.
|
||||||
|
|
||||||
/// The struct representing an Icon Reference String
|
/// The struct representing an Icon Reference String
|
||||||
/// looks like `%SystemRoot%\System32\imageres.dll,-72`.
|
/// looks like `%SystemRoot%\System32\imageres.dll,-72`.
|
||||||
///
|
///
|
||||||
/// As far as I know, the minus token `-` does nothing in this string.
|
/// As far as I know, the minus token `-` does nothing in this string.
|
||||||
/// The following number is just the index.
|
/// The following number is just the index.
|
||||||
|
///
|
||||||
|
/// If you have a Icon Reference String,
|
||||||
|
/// please utilize [FromStr] trait to parse it into this struct.
|
||||||
|
/// You also can use [IconRefStr::new] with path and index parts to build this struct.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// outputs Icon Reference String in string form.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct IconRefStr {
|
pub struct IconRefStr {
|
||||||
/// The path part of this reference string.
|
/// The path part of this reference string.
|
||||||
@@ -352,6 +393,12 @@ impl IconRefStr {
|
|||||||
/// Get the path part of this reference string.
|
/// Get the path part of this reference string.
|
||||||
///
|
///
|
||||||
/// This path can be absolute path, relative path or expandable path.
|
/// This path can be absolute path, relative path or expandable path.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently this return value may be quoted path.
|
||||||
|
/// You may need manually unquote it.
|
||||||
|
/// This issue will be resolved in furture.
|
||||||
pub fn get_path(&self) -> &str {
|
pub fn get_path(&self) -> &str {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
@@ -395,7 +442,7 @@ impl FromStr for IconRefStr {
|
|||||||
|
|
||||||
/// Error occurs when given string is not a valid String Reference String.
|
/// Error occurs when given string is not a valid String Reference String.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given string \"{inner}\" is not a valid String Reference String")]
|
#[error(r#"given string "{inner}" is not a valid String Reference String"#)]
|
||||||
pub struct ParseStrRefStrError {
|
pub struct ParseStrRefStrError {
|
||||||
/// The clone of string which is not a valid String Reference String
|
/// The clone of string which is not a valid String Reference String
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -410,11 +457,22 @@ impl ParseStrRefStrError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// StrRefStr should process the quote issue for path part in itself,
|
||||||
|
// rather than fetching its path part and resolve quote issue in another place.
|
||||||
|
|
||||||
/// The struct representing an String Reference String
|
/// The struct representing an String Reference String
|
||||||
/// looks like `@%SystemRoot%\System32\shell32.dll,-30596`.
|
/// looks like `@%SystemRoot%\System32\shell32.dll,-30596`.
|
||||||
///
|
///
|
||||||
/// As far as I know, the minus token `-` does nothing in this string.
|
/// As far as I know, the minus token `-` does nothing in this string.
|
||||||
/// The following number is just the index.
|
/// The following number is just the index.
|
||||||
|
///
|
||||||
|
/// If you have a String Reference String,
|
||||||
|
/// please utilize [FromStr] trait to parse it into this struct.
|
||||||
|
/// You also can use [StrRefStr::new] with path and index parts to build this struct.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// outputs String Reference String in string form.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct StrRefStr {
|
pub struct StrRefStr {
|
||||||
/// The path part of this reference string.
|
/// The path part of this reference string.
|
||||||
@@ -440,6 +498,12 @@ impl StrRefStr {
|
|||||||
/// Get the path part of this reference string.
|
/// Get the path part of this reference string.
|
||||||
///
|
///
|
||||||
/// This path can be absolute path, relative path or expandable path.
|
/// This path can be absolute path, relative path or expandable path.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently this return value may be quoted path.
|
||||||
|
/// You may need manually unquote it.
|
||||||
|
/// This issue will be resolved in furture.
|
||||||
pub fn get_path(&self) -> &str {
|
pub fn get_path(&self) -> &str {
|
||||||
&self.path
|
&self.path
|
||||||
}
|
}
|
||||||
@@ -504,14 +568,40 @@ pub enum IconSizeKind {
|
|||||||
Large,
|
Large,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The struct representing a loaded icon resource.
|
/// The struct representing a loaded Win32 icon resource.
|
||||||
|
///
|
||||||
|
/// You can use [IconRc::new] or [IconRc::with_ico_file] to create this resource.
|
||||||
|
///
|
||||||
|
/// The ownership of loaded Win32 icon resource is managed by this struct,
|
||||||
|
/// and will be free once this struct was destroyed.
|
||||||
|
///
|
||||||
|
/// According to Microsoft manual,
|
||||||
|
/// Win32 icon resource is program scope resource,
|
||||||
|
/// so it can be safely used between different threads.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct IconRc {
|
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.
|
||||||
|
///
|
||||||
|
/// Please note that the content of `file` parameter must NOT be
|
||||||
|
/// expandable string or quoted string.
|
||||||
///
|
///
|
||||||
/// If you want to extract icon from `.ico` file, please pass `0` to `index` parameter.
|
/// If you want to extract icon from `.ico` file, please pass `0` to `index` parameter.
|
||||||
/// Otherwise `index` is the icon resource index located in executable.
|
/// Otherwise `index` is the icon resource index located in executable.
|
||||||
@@ -548,16 +638,28 @@ impl IconRc {
|
|||||||
Self::new(file, 0, kind)
|
Self::new(file, 0, kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct this struct in raw Win32 icon handle.
|
||||||
|
///
|
||||||
|
/// Please note that after calling this function,
|
||||||
|
/// the ownership of given Win32 icon handle are managed by this struct.
|
||||||
pub unsafe fn from_raw(hicon: HICON) -> Self {
|
pub unsafe fn from_raw(hicon: HICON) -> Self {
|
||||||
Self { icon: hicon }
|
Self { icon: hicon }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_raw(self) -> HICON {
|
/// Consume and leak this struct held Win32 icon handle.
|
||||||
self.icon
|
///
|
||||||
|
/// The ownership of this Win32 icon handle will be transfered to the caller of this function.
|
||||||
|
pub fn into_raw(mut self) -> HICON {
|
||||||
|
let rv = self.icon;
|
||||||
|
self.icon = std::ptr::null_mut();
|
||||||
|
rv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IconRc {
|
impl IconRc {
|
||||||
|
/// Get underlying Win32 icon handle.
|
||||||
|
///
|
||||||
|
/// Please note that the ownership of this handle are still in this struct.
|
||||||
pub fn get_icon(&self) -> HICON {
|
pub fn get_icon(&self) -> HICON {
|
||||||
self.icon
|
self.icon
|
||||||
}
|
}
|
||||||
@@ -608,12 +710,18 @@ pub enum LoadStrRcError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The struct representing a loaded string resource.
|
/// The struct representing a loaded string resource.
|
||||||
|
///
|
||||||
|
/// You can use [StrRc::new] to create this resource.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StrRc {
|
pub struct StrRc {
|
||||||
inner: String,
|
inner: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StrRc {
|
impl StrRc {
|
||||||
|
/// Load icon from executable file.
|
||||||
|
///
|
||||||
|
/// Please note that the content of `file` parameter must NOT be
|
||||||
|
/// expandable string or quoted string.
|
||||||
pub fn new(file: &str, index: u32) -> Result<Self, LoadStrRcError> {
|
pub fn new(file: &str, index: u32) -> Result<Self, LoadStrRcError> {
|
||||||
use windows_sys::Win32::Foundation::FreeLibrary;
|
use windows_sys::Win32::Foundation::FreeLibrary;
|
||||||
use windows_sys::Win32::System::LibraryLoader::{
|
use windows_sys::Win32::System::LibraryLoader::{
|
||||||
@@ -659,10 +767,12 @@ impl StrRc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StrRc {
|
impl StrRc {
|
||||||
|
/// Get fetched string resource.
|
||||||
pub fn get_string(&self) -> &str {
|
pub fn get_string(&self) -> &str {
|
||||||
&self.inner
|
&self.inner
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consume and return fetched string resource.
|
||||||
pub fn into_string(self) -> String {
|
pub fn into_string(self) -> String {
|
||||||
self.inner
|
self.inner
|
||||||
}
|
}
|
||||||
@@ -703,9 +813,15 @@ pub enum ExpandEnvVarError {
|
|||||||
NoEnvVar,
|
NoEnvVar,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The struct representing an Expand String,
|
/// The struct representing an expandable string,
|
||||||
/// which contain environment variable in string,
|
/// which contain environment variable in string,
|
||||||
/// like `%LOCALAPPDATA%\SomeApp.exe`.
|
/// like `%LOCALAPPDATA%\SomeApp.exe`.
|
||||||
|
///
|
||||||
|
/// You can use [ExpandString::new] or [FromStr] trait to create an expandable string,
|
||||||
|
/// and use [ExpandString::expand] to try expanding it.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// outputs original expandable string.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct ExpandString {
|
pub struct ExpandString {
|
||||||
inner: String,
|
inner: String,
|
||||||
@@ -723,7 +839,7 @@ 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 string without having any embedded variable syntax.
|
||||||
pub fn expand(&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;
|
||||||
|
|
||||||
@@ -784,9 +900,15 @@ impl FromStr for ExpandString {
|
|||||||
|
|
||||||
// region: File Name
|
// region: File Name
|
||||||
|
|
||||||
|
/// The error occurs when constructing FileName with bad file name.
|
||||||
pub type BadFileNameError = ParseFileNameError;
|
pub type BadFileNameError = ParseFileNameError;
|
||||||
|
|
||||||
/// The struct representing a legal Windows file name.
|
/// The struct representing a legal Windows file name.
|
||||||
|
///
|
||||||
|
/// You can utilize [FileName::new] or [FromStr] trait to create this struct.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// outputs the file name in string form.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct FileName {
|
pub struct FileName {
|
||||||
/// The validated legal Windows file name.
|
/// The validated legal Windows file name.
|
||||||
@@ -810,7 +932,7 @@ impl FileName {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error occurs when constructing FileName with bad file name.
|
/// The error occurs when parsing FileName with bad file name.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given file name is illegal in Windows")]
|
#[error("given file name is illegal in Windows")]
|
||||||
pub enum ParseFileNameError {
|
pub enum ParseFileNameError {
|
||||||
@@ -857,9 +979,15 @@ impl FromStr for FileName {
|
|||||||
|
|
||||||
// region: Verb
|
// region: Verb
|
||||||
|
|
||||||
|
/// The error occurs when constructing Verb with bad verb name.
|
||||||
pub type BadVerbError = ParseVerbError;
|
pub type BadVerbError = ParseVerbError;
|
||||||
|
|
||||||
/// The struct representing a verb when manipulating file
|
/// The struct representing a verb when manipulating file
|
||||||
|
///
|
||||||
|
/// You can utilize [Verb::new] or [FromStr] trait to create this struct.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// outputs the verb in string form.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Verb {
|
pub struct Verb {
|
||||||
/// The validated verb name.
|
/// The validated verb name.
|
||||||
@@ -867,16 +995,19 @@ pub struct Verb {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Verb {
|
impl Verb {
|
||||||
|
/// Get the verb representing Open.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn OPEN() -> Self {
|
pub fn OPEN() -> Self {
|
||||||
Verb::new("open").expect("unexpected bad verb")
|
Verb::new("open").expect("unexpected bad verb")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the verb representing Edit.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn EDIT() -> Self {
|
pub fn EDIT() -> Self {
|
||||||
Verb::new("edit").expect("unexpected bad verb")
|
Verb::new("edit").expect("unexpected bad verb")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the verb representing Play.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub fn PLAY() -> Self {
|
pub fn PLAY() -> Self {
|
||||||
Verb::new("play").expect("unexpected bad verb")
|
Verb::new("play").expect("unexpected bad verb")
|
||||||
@@ -898,9 +1029,9 @@ impl Verb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error occurs when constructing Verb with bad verb name.
|
/// The error occurs when parsing Verb with bad verb name.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given verb \"{inner}\" is illegal")]
|
#[error(r#"given verb "{inner}" is illegal"#)]
|
||||||
pub struct ParseVerbError {
|
pub struct ParseVerbError {
|
||||||
inner: String,
|
inner: String,
|
||||||
}
|
}
|
||||||
@@ -939,52 +1070,106 @@ impl FromStr for Verb {
|
|||||||
|
|
||||||
// region: Command Line
|
// region: Command Line
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Introduce CmdArg in future to describe single Windows command line argument.
|
||||||
|
// And use CmdArg to finish the full functionality of CmdLine.
|
||||||
|
|
||||||
/// The error occurs when constructing CmdLine with bad arguments.
|
/// The error occurs when constructing CmdLine with bad arguments.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given file extension body \"{inner}\" is invalid")]
|
#[error(r#"given command line argument "{inner}" is invalid"#)]
|
||||||
pub struct BadCmdLineError {
|
pub struct BadCmdLineError {
|
||||||
inner: String,
|
inner: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BadCmdLineError {
|
||||||
|
// TODO: Remove this dead_code attribute once we need use this error type.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn new(inner: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: inner.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The struct representing a Windows command line.
|
/// The struct representing a Windows command line.
|
||||||
|
///
|
||||||
|
/// If you have a complete Windows command line expressed in string form,
|
||||||
|
/// you can utilize [FromStr] trait to create this struct.
|
||||||
|
/// Or, you can utilize [CmdLine::new] to create this struct with command arguments one by one.
|
||||||
|
///
|
||||||
|
/// The [Display] trait this struct implemented always
|
||||||
|
/// outputs a legal Windows command line in string form,
|
||||||
|
/// which combine all arguments together.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Todo
|
||||||
///
|
///
|
||||||
/// This struct currently does nothing for validation for given command line.
|
/// This struct currently does nothing for validation for given command line.
|
||||||
/// Because there is no standard for validating this.
|
/// Because there is no standard for validating this.
|
||||||
/// So I just write this struct as a placeholder for future extension.
|
/// So I just write this struct as a placeholder for future extension.
|
||||||
|
///
|
||||||
|
/// Currently if you are using [FromStr] trait to parse command line,
|
||||||
|
/// it will simply split it with space as separator.
|
||||||
|
/// This may cause unexpected behavior such as empty argument or unexpected break in argument.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct CmdLine {
|
pub struct CmdLine {
|
||||||
inner: Vec<String>,
|
inner: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CmdLine {
|
impl CmdLine {
|
||||||
/// Create a new command line.
|
/// Create a new command line with arguments one by one.
|
||||||
|
///
|
||||||
|
/// Please note that passed command line argument must be unquoted.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently, there is no unquote restrictions for those passed arguments.
|
||||||
|
/// These passed argument will be honestly used in full command line.
|
||||||
|
/// So if your argument require quote, please quote them at first.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
pub fn new<S: AsRef<str>>(args: &[S]) -> Result<Self, BadCmdLineError> {
|
pub fn new<S: AsRef<str>>(args: &[S]) -> Result<Self, BadCmdLineError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: args.iter().map(|s| s.as_ref().to_string()).collect(),
|
inner: args.iter().map(|s| s.as_ref().to_string()).collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the full command line.
|
/// Get the full command line in string form with proper quote.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently this function only simply returns
|
||||||
|
/// the joined command line arguments with space as separator.
|
||||||
|
/// There is no guarantee about safe quote.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
pub fn full(&self) -> String {
|
pub fn full(&self) -> String {
|
||||||
self.inner.join(" ")
|
self.inner.join(" ")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the iterator of command line arguments.
|
/// Get the iterator of command line arguments.
|
||||||
|
///
|
||||||
|
/// Each returned argument are unquoted.
|
||||||
|
///
|
||||||
|
/// # Todo
|
||||||
|
///
|
||||||
|
/// Currently there is no guarantee about the unquote behavior
|
||||||
|
/// for returned command line arguments.
|
||||||
|
/// It simply returns the content of arguments list passed to constructor.
|
||||||
|
/// This issue will be resolved in future.
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &str> {
|
pub fn iter(&self) -> impl Iterator<Item = &str> {
|
||||||
self.inner.iter().map(|s| s.as_str())
|
self.inner.iter().map(|s| s.as_str())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The error occurs when constructing CmdLine with bad syntax.
|
/// The error occurs when parsing CmdLine with bad syntax.
|
||||||
#[derive(Debug, TeError)]
|
#[derive(Debug, TeError)]
|
||||||
#[error("given command line \"{inner}\" is invalid")]
|
#[error(r#"given command line "{inner}" is invalid"#)]
|
||||||
pub struct ParseCmdLineError {
|
pub struct ParseCmdLineError {
|
||||||
inner: String,
|
inner: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParseCmdLineError {
|
impl ParseCmdLineError {
|
||||||
|
// TODO: Remove this dead_code attribute once we need use this error type.
|
||||||
|
#[allow(dead_code)]
|
||||||
fn new(inner: &str) -> Self {
|
fn new(inner: &str) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: inner.to_string(),
|
inner: inner.to_string(),
|
||||||
@@ -1008,3 +1193,5 @@ impl FromStr for CmdLine {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion
|
||||||
|
|||||||
+80
-20
@@ -1,8 +1,6 @@
|
|||||||
//! This module extend `winreg` crate to make it more suit for the usage of this crate.
|
//! This module extend `winreg` crate to make it more suit for the usage of this crate.
|
||||||
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::ops::Deref;
|
|
||||||
use std::ops::DerefMut;
|
|
||||||
use thiserror::Error as TeError;
|
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;
|
||||||
@@ -12,13 +10,13 @@ use winreg::types::FromRegValue;
|
|||||||
|
|
||||||
// region: Extra Operations
|
// region: Extra Operations
|
||||||
|
|
||||||
/// Get the subkey with given name.
|
/// Try getting the subkey with given name.
|
||||||
///
|
///
|
||||||
/// If error occurs when fetching given subkey, it return `Err(...)`,
|
/// If error occurs when fetching given subkey, it return `Err(...)`,
|
||||||
/// otherwise, it will return `Ok(Some(...))` if subkey is existing,
|
/// otherwise, it will return `Ok(Some(...))` if subkey is existing,
|
||||||
/// or `Ok(None)` if there is no suchsub key.
|
/// or `Ok(None)` if there is no suchsub key.
|
||||||
///
|
///
|
||||||
/// Comparing with the function provided by winreg,
|
/// Comparing with the function provided by [winreg],
|
||||||
/// it differ "no such subkey" error and other access error.
|
/// it differ "no such subkey" error and other access error.
|
||||||
pub fn try_open_subkey_with_flags<P: AsRef<OsStr>>(
|
pub fn try_open_subkey_with_flags<P: AsRef<OsStr>>(
|
||||||
regkey: &RegKey,
|
regkey: &RegKey,
|
||||||
@@ -37,13 +35,13 @@ pub fn try_open_subkey_with_flags<P: AsRef<OsStr>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value by given key.
|
/// Try getting the value by given key.
|
||||||
///
|
///
|
||||||
/// If error occurs when fetching given key, it return `Err(...)`,
|
/// If error occurs when fetching given key, it return `Err(...)`,
|
||||||
/// otherwise, it will return `Ok(Some(...))` if key is existing,
|
/// otherwise, it will return `Ok(Some(...))` if key is existing,
|
||||||
/// or `Ok(None)` if there is no such key.
|
/// or `Ok(None)` if there is no such key.
|
||||||
///
|
///
|
||||||
/// Comparing with the function provided by winreg,
|
/// Comparing with the function provided by [winreg],
|
||||||
/// it differ "no such key" error and other access error.
|
/// it differ "no such key" error and other access error.
|
||||||
pub fn try_get_value<T: FromRegValue, N: AsRef<OsStr>>(
|
pub fn try_get_value<T: FromRegValue, N: AsRef<OsStr>>(
|
||||||
regkey: &RegKey,
|
regkey: &RegKey,
|
||||||
@@ -61,13 +59,65 @@ 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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Once we support multiple ShellVerb,
|
||||||
|
// it seems that there is no need to preserve this function.
|
||||||
|
|
||||||
/// Get the name of only subkey in given key.
|
/// 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 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 there is no any subkey, or has multiple subkeys, return None instead.
|
||||||
/// If error occurs when fetching data, return Err(_).
|
/// If error occurs when fetching data, return `Err(_)`.
|
||||||
///
|
///
|
||||||
/// This is usually used for ShellVerb fetching.
|
/// This is used as fetching only one ShellVerb.
|
||||||
pub fn get_sole_subkey_name(regkey: &RegKey) -> std::io::Result<Option<String>> {
|
pub fn get_sole_subkey_name(regkey: &RegKey) -> std::io::Result<Option<String>> {
|
||||||
let mut subkey_enumerator = regkey.enum_keys();
|
let mut subkey_enumerator = regkey.enum_keys();
|
||||||
|
|
||||||
@@ -85,8 +135,8 @@ pub fn get_sole_subkey_name(regkey: &RegKey) -> std::io::Result<Option<String>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the name list of all "string" subkeys in given key.
|
/// Get the name list of all "string" subkeys in given key.
|
||||||
///
|
///
|
||||||
/// This is usually used for "OpenWithProgIds" subkey.
|
/// This is used in "OpenWithProgIds" subkey.
|
||||||
pub fn get_all_string_subkey_names(regkey: &RegKey) -> std::io::Result<Vec<String>> {
|
pub fn get_all_string_subkey_names(regkey: &RegKey) -> std::io::Result<Vec<String>> {
|
||||||
regkey
|
regkey
|
||||||
.enum_values()
|
.enum_values()
|
||||||
@@ -105,11 +155,14 @@ pub fn get_all_string_subkey_names(regkey: &RegKey) -> std::io::Result<Vec<Strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Delete all contents, including values and subkeys of given key.
|
/// 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].
|
/// 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.
|
/// 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.
|
/// 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<()> {
|
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("")
|
regkey.delete_subkey_all("")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,15 +182,15 @@ impl BlankPathError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether given registry path is empty.
|
/// Check whether given registry path is empty.
|
||||||
/// If it is, return error, otherwise the given path.
|
/// If it is, return error, otherwise the given path itself.
|
||||||
///
|
///
|
||||||
/// Passing empty path for some registry functions is dangerous.
|
/// Passing empty path for some [winreg] functions is dangerous, expecially for those registry delete functions.
|
||||||
/// Because it will cause unexpected behavior that returning key self, rather than subkey.
|
/// Passing empty path to registry creation function will return themselves,
|
||||||
/// This is VERY dangerous especially for those registry delete functions.
|
/// and passing them to registry delete functions will delete all contents of them!
|
||||||
/// 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 [winreg] function.
|
||||||
///
|
///
|
||||||
/// This function MUST be used for the value, whose content can not be confirmed at compile time,
|
/// This function MUST be used for wrapping 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.
|
/// if that value 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())
|
||||||
@@ -150,6 +203,11 @@ pub fn blank_path_guard<P: AsRef<OsStr>>(path: P) -> std::result::Result<P, Blan
|
|||||||
|
|
||||||
// region: Expand String
|
// region: Expand String
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// Re-enable following code when we need to distinguish between REG_SZ and REG_EXPAND_SZ.
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
/// The struct basically is the alias of String, but make a slight difference with it,
|
/// The struct basically is the alias of String, but make a slight difference with it,
|
||||||
/// to make they are different when use it with String as generic argument.
|
/// to make they are different when use it with String as generic argument.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
@@ -217,4 +275,6 @@ impl From<&str> for ExpandString {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
@@ -7,7 +7,12 @@
|
|||||||
/// It usually means that checking whether current process is running as Administrator.
|
/// It usually means that checking whether current process is running as Administrator.
|
||||||
/// Return true if it is, otherwise false.
|
/// Return true if it is, otherwise false.
|
||||||
///
|
///
|
||||||
/// Reference: https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership
|
/// Reference: <https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-checktokenmembership>
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This function will panic if any Windows functions failed unexpectedly.
|
||||||
|
/// This is a very common check and in theory there is no any possibility that this check will fail.
|
||||||
pub fn has_privilege() -> bool {
|
pub fn has_privilege() -> bool {
|
||||||
use windows_sys::Win32::Foundation::HANDLE;
|
use windows_sys::Win32::Foundation::HANDLE;
|
||||||
use windows_sys::Win32::Security::{
|
use windows_sys::Win32::Security::{
|
||||||
|
|||||||
@@ -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. ",
|
||||||
|
r#"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."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -186,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())
|
||||||
@@ -195,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);
|
||||||
@@ -312,12 +318,17 @@ fn test_verb() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cmd_line() {
|
fn test_cmd_line() {
|
||||||
|
// TODO:
|
||||||
|
// Remove error tester dead_code attribute,
|
||||||
|
// improve ok tester and add more error tests
|
||||||
|
// once we finish a complete CmdLine.
|
||||||
fn ok_tester(s: &str) {
|
fn ok_tester(s: &str) {
|
||||||
let rv = CmdLine::from_str(s);
|
let rv = CmdLine::from_str(s);
|
||||||
assert!(rv.is_ok());
|
assert!(rv.is_ok());
|
||||||
// let rv = rv.unwrap();
|
// let rv = rv.unwrap();
|
||||||
// assert_eq!(s, rv.full());
|
// assert_eq!(s, rv.full());
|
||||||
}
|
}
|
||||||
|
#[allow(dead_code)]
|
||||||
fn err_tester(s: &str) {
|
fn err_tester(s: &str) {
|
||||||
let rv = CmdLine::from_str(s);
|
let rv = CmdLine::from_str(s);
|
||||||
assert!(rv.is_err());
|
assert!(rv.is_err());
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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