Compare commits
16
Commits
119a4d0341
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fcdf902f7 | ||
|
|
e3a1195c04 | ||
|
|
db69452213 | ||
|
|
a350a591ad | ||
|
|
6a354bd7a7 | ||
|
|
23d7045ec1 | ||
|
|
3492f93c83 | ||
|
|
d1de56a21d | ||
|
|
880f90211b | ||
|
|
1a44240f88 | ||
|
|
071347f4d4 | ||
|
|
821b865f2d | ||
|
|
50f01429b0 | ||
|
|
930a84a0f6 | ||
|
|
447d94fdd6 | ||
|
|
f0bd2c0b73 |
+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"
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
我要求你使用Qt Widget编写一个GUI程序。该GUI程序是wfassoc的一个可视化界面。wfassoc是一个由Rust编写,并暴露出C接口,可以操作Windows注册表,来管理应用程序的注册和卸载,以及文件关联的动态链接库。
|
|
||||||
|
|
||||||
我要求你在@example/qwfassoc/TASKS.md 中先做好详细的规划,而不是上来就写代码。我会安排其他人来负责执行你做的规划。
|
|
||||||
|
|
||||||
# 项目要求
|
|
||||||
|
|
||||||
- 使用Qt Widget编写界面,而不是QML。
|
|
||||||
- 使用UI文件而不是C++语句来构建界面。
|
|
||||||
- 我使用的是Qt 6,使用CMake作为构建系统,不要使用Qt的qmake。
|
|
||||||
- 使用toml11作为TOML读取库。
|
|
||||||
|
|
||||||
# 界面要求
|
|
||||||
|
|
||||||
这是一个基于 Qt Widgets 的标准对话框界面描述。你可以按照以下层级结构来构建代码:
|
|
||||||
|
|
||||||
## 主窗口容器 (Main Window)
|
|
||||||
|
|
||||||
* 类: `QDialog`。
|
|
||||||
* 窗口标题: "xxx选项"。xxx在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
|
||||||
* 窗口图标:在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
|
||||||
* 大小限制:固定大小480x600
|
|
||||||
|
|
||||||
## 选项卡 (Top Tabs)
|
|
||||||
|
|
||||||
* 组件: `QTabWidget`。
|
|
||||||
* 标签页 (Tabs): 从左到右依次添加以下标签页:
|
|
||||||
1. 应用程序
|
|
||||||
2. 文件关联
|
|
||||||
* 大小:选项卡占据对话框全部内容
|
|
||||||
|
|
||||||
## "应用程序"选项卡内容
|
|
||||||
|
|
||||||
该选项卡内部使用垂直布局 (`QVBoxLayout`),包含一个主要的分组区域:
|
|
||||||
|
|
||||||
### 区域内容
|
|
||||||
|
|
||||||
* 容器: `QGroupBox`。
|
|
||||||
* 区域标题:安装与卸载
|
|
||||||
* 布局: 垂直布局 (`QVBoxLayout`)。
|
|
||||||
* 上半部分
|
|
||||||
* 水平布局(`QHBoxLayout`)。
|
|
||||||
* 左侧: 一个 `QLabel` 显示图标,该图标表示要设定的应用程序的图标。在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
|
||||||
* 右侧: 一个 `QLabel` 显示文本"在此安装或卸载xxx",表示要设定的应用程序的名称。xxx在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。 (文本需要设置自动换行 `setWordWrap(true)`)。
|
|
||||||
* 下半部分
|
|
||||||
* 水平布局(`QHBoxLayout`)。
|
|
||||||
* 内容为两个 `QPushButton`,文本分别为:
|
|
||||||
* 安装:为当前对象(系统或当前用户,由应用程序初始化时从命令行参数获取)安装应用。如果应用程序已经安装,则不可点击。
|
|
||||||
* 卸载:为当前对象(系统或当前用户,由应用程序初始化时从命令行参数获取)卸载应用。如果应用程序没有安装,则不可点击。
|
|
||||||
|
|
||||||
## "文件关联"选项卡内容
|
|
||||||
|
|
||||||
在这个选项页内部,使用一个垂直布局 (`QVBoxLayout`) 来排列以下控件:
|
|
||||||
|
|
||||||
* 说明文本:
|
|
||||||
* 组件: `QLabel`。
|
|
||||||
* 文本: "使用 xxx 关联的文件类型:"。xxx在应用程序初始化时,通过wfassoc的Program提供的接口,运行时获取。
|
|
||||||
|
|
||||||
* 功能按钮行:
|
|
||||||
* 布局: `QHBoxLayout` (水平布局)。
|
|
||||||
* 组件: 两个 `QPushButton`。
|
|
||||||
* 文本: 两个按钮的文本都是 "+"。
|
|
||||||
* 位置: 位于列表上方,用于“全选”操作(第一次点击,将所有没关联的文件扩展名(显示为空白)设置为应用程序提供的打开方式。如果没有空白内容,或第二次点击,将所有文件全部设置为应用程序提供的打开方式)。
|
|
||||||
|
|
||||||
* 文件类型列表 (核心组件):
|
|
||||||
* 组件: `QTableWidget` (表格控件)。
|
|
||||||
* 列数: 3列。
|
|
||||||
* 表头 (Header):
|
|
||||||
* 第1列标题: "类型"
|
|
||||||
* 第2列标题: "uuu" (uuu在运行时进行获取,为当前用户名)
|
|
||||||
* 第3列标题: "所有用户"
|
|
||||||
* 行内容示例:
|
|
||||||
* 第一列:一个文件类型图标,右边跟着对应的文本。表示当前文件扩展名,和当前混合视图(hybrid)下的图标。图标和文本均从wfassoc函数获取。
|
|
||||||
* 第二列:用户视图(user)下的名称。文本从wfassoc函数获取。
|
|
||||||
* 第三列:系统视图(system)下的名称。文本从wfassoc函数获取。
|
|
||||||
* 滚动条: 右侧有一个垂直滚动条 (`QScrollBar`),表示内容超出可视区域。
|
|
||||||
* 操作方式:
|
|
||||||
* 第二列和第三列的元素可点击。
|
|
||||||
* 如果元素为空白或其它打开方式,则点击后设置为当前应用程序指定的打开方式(link)。
|
|
||||||
* 如果是自身打开方式,点击后设置为空白(unlink)
|
|
||||||
* 点击后,第一列的图标需要改变,也因此你需要暂存当前应用程序提供打开方式的图标。如果第二第三列均为空,则不显示图标(仍然占位,显示为空白)。
|
|
||||||
* 点击操作并不会实时操作注册表,程序需要暂存用户的需求,然后在点击确认或应用按钮后再统一执行。
|
|
||||||
|
|
||||||
* 底部按钮栏
|
|
||||||
* 布局: `QHBoxLayout` (水平布局),通常右对齐或使用 `QDialogButtonBox`。
|
|
||||||
* 组件: 三个 `QPushButton`。
|
|
||||||
* 按钮文本 (从左到右):
|
|
||||||
1. "确定" (通常设为默认按钮 `setDefault(true)`)。
|
|
||||||
2. "取消"。
|
|
||||||
3. "应用":点击后应用修改,并留在页面 (如果没有修改,则不可用)。
|
|
||||||
|
|
||||||
额外注意:
|
|
||||||
|
|
||||||
* 如果应用程序没有安装,则本页面下所有内容均不启用。
|
|
||||||
* 如果启动时命令行指定以为当前用户安装的模式启动,则系统那一栏所有按钮都不可用
|
|
||||||
|
|
||||||
# 代码要求
|
|
||||||
|
|
||||||
- 有关wfassoc的接口,请查阅@wfassoc-cdylib/codegen/wfassoc++.h 我要求你使用这个头文件中提供的内容来进行编写。
|
|
||||||
- wfassoc++.h文件中没有注释,如果你想查看注释,请访问@wfassoc-cdylib/codegen/wfassoc.h 文件。wfassoc++.h是wfassoc.h的C++包装。
|
|
||||||
- wfassoc.h所暴露的接口是由Rust编写的,通常查看wfassoc.h可满足所有需求。如果仍有不确定的内容,可查看其对应Rust项目的源码,位于@wfassoc-cdylib/src 目录下。或更进一步地查看其依赖的源码,位于@wfassoc/src 目录下。
|
|
||||||
- 该GUI程序需要接受两个必要的命令行参数,请使用Qt内置的命令行解析器进行解析:
|
|
||||||
- `-c`或`--manifest`:指定要配置的应用程序的清单文件。
|
|
||||||
- `-f`或`--for`:指定应用程序要安装到的
|
|
||||||
- 清单文件的样例是@example/manifest/ppic.toml
|
|
||||||
- 在应用程序加载时,或者执行操作时,如果发生错误(例如底层wfassoc发生错误,丢失命令行选项等,则弹出对话框报错,然后立即退出程序)
|
|
||||||
- 程序基本流程:
|
|
||||||
- 接受命令行,检查命令行参数是否合法
|
|
||||||
- 加载命令行指定的manifest文件,并使用sanitizer检查错误。你可以阅读@wfassoc-exec/src/manifest.rs 文件来看看我是如何在Rust中检查它的。
|
|
||||||
- 按照给定的manifest文件,使用wfassoc库构建schema,然后再构建program。
|
|
||||||
- 初始化窗口。
|
|
||||||
- 调用wfassoc program提供的函数,检查应用程序是否安装,设置窗口的安装部分的按钮enable。
|
|
||||||
- 调用wfassoc program提供的函数,遍历所有文件扩展名和关联情况,设置窗口的文件关联表格。
|
|
||||||
- 用户可以在"应用程序"选项卡中注册和卸载应用程序,点击后弹出窗口表示安装或卸载成功,然后检测是否安装,并刷新各个控件的enable状态。
|
|
||||||
- 用户可以在"文件关联"选项卡中设置是否以当前应用程序打开某些扩展名。用户可以点击全选按钮或单元格来进行设置,应用程序暂存修改,等用户点击确认或应用后再应用修改。如果点击的是应用,则刷新当前页面。
|
|
||||||
|
|
||||||
# 额外要求
|
|
||||||
|
|
||||||
- 不要尝试去编译来检查错误。我会安排其他人来检查程序是否能正常运行,并汇报回来,你再修改。
|
|
||||||
- 你不需要关心能否找到Qt,wfassoc和toml11这些库。
|
|
||||||
- 对于wfassoc,你只需要将@wfassoc-cdylib/codegen/Findwfassoc.cmake 复制到@example/qwfassoc/cmake 目录下,并在此目录下编写一个README.md,表明这个文件是从哪里复制来的即可。然后把复制的cmake文件所在目录加入find_package目录,然后使用find_package寻找wfassoc即可。至于去哪里找这个库,我会安排其他人来做。
|
|
||||||
- 对于Qt和toml11我会安排其他人来做,你只需要用find_package来找他们就行,不需要操心能不能找到。
|
|
||||||
- 如果你对某项需求有疑问,请问我,而不是进行猜测。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 总结代码结构示意 (伪代码):
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
QDialog *dialog = new QDialog();
|
|
||||||
dialog->setWindowTitle("选项");
|
|
||||||
|
|
||||||
QVBoxLayout *mainLayout = new QVBoxLayout(dialog);
|
|
||||||
|
|
||||||
// 1. Tab Widget
|
|
||||||
QTabWidget *tabWidget = new QTabWidget();
|
|
||||||
tabWidget->addTab(new QWidget(), "系统");
|
|
||||||
tabWidget->addTab(new QWidget(), "7-Zip");
|
|
||||||
// ... 其他 tabs
|
|
||||||
|
|
||||||
// 2. System Tab Content
|
|
||||||
QWidget *systemTab = tabWidget->widget(0);
|
|
||||||
QVBoxLayout *systemLayout = new QVBoxLayout(systemTab);
|
|
||||||
|
|
||||||
// Label
|
|
||||||
QLabel *label = new QLabel("使用 7-Zip 关联的文件类型:");
|
|
||||||
systemLayout->addWidget(label);
|
|
||||||
|
|
||||||
// Buttons (+)
|
|
||||||
QHBoxLayout *btnLayout = new QHBoxLayout();
|
|
||||||
QPushButton *btnPlus1 = new QPushButton("+");
|
|
||||||
QPushButton *btnPlus2 = new QPushButton("+");
|
|
||||||
btnLayout->addWidget(btnPlus1);
|
|
||||||
btnLayout->addWidget(btnPlus2);
|
|
||||||
systemLayout->addLayout(btnLayout);
|
|
||||||
|
|
||||||
// List (TreeWidget)
|
|
||||||
QTreeWidget *treeWidget = new QTreeWidget();
|
|
||||||
treeWidget->setColumnCount(3);
|
|
||||||
treeWidget->setHeaderLabels(QStringList() << "类型" << "yyc12345" << "所有用户");
|
|
||||||
// 添加 items...
|
|
||||||
systemLayout->addWidget(treeWidget);
|
|
||||||
|
|
||||||
// 3. Bottom Buttons
|
|
||||||
QHBoxLayout *bottomLayout = new QHBoxLayout();
|
|
||||||
bottomLayout->addStretch(); // 推挤按钮到右边
|
|
||||||
QPushButton *btnOK = new QPushButton("确定");
|
|
||||||
QPushButton *btnCancel = new QPushButton("取消");
|
|
||||||
QPushButton *btnApply = new QPushButton("应用(A)");
|
|
||||||
btnApply->setEnabled(false); // 禁用
|
|
||||||
QPushButton *btnHelp = new QPushButton("帮助");
|
|
||||||
|
|
||||||
bottomLayout->addWidget(btnOK);
|
|
||||||
bottomLayout->addWidget(btnCancel);
|
|
||||||
bottomLayout->addWidget(btnApply);
|
|
||||||
bottomLayout->addWidget(btnHelp);
|
|
||||||
|
|
||||||
mainLayout->addWidget(tabWidget);
|
|
||||||
mainLayout->addLayout(bottomLayout);
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
这是一个标准的 Windows 风格属性对话框,可以通过以下 Qt Widgets 结构来描述:
|
|
||||||
|
|
||||||
### 1. 主窗口容器
|
|
||||||
* 类: `QDialog`。
|
|
||||||
* 标题: "系统属性"。
|
|
||||||
* 布局: 垂直布局 (`QVBoxLayout`)。
|
|
||||||
|
|
||||||
### 2. 顶部选项卡 (Tabs)
|
|
||||||
* 组件: `QTabWidget`。
|
|
||||||
* 标签页: 包含 "计算机名", "硬件", "高级", "系统保护", "远程"。
|
|
||||||
* 当前选中: "硬件" 标签页。
|
|
||||||
|
|
||||||
### 3. "硬件" 选项卡内容
|
|
||||||
该选项卡内部使用垂直布局 (`QVBoxLayout`),包含两个主要的分组区域(视觉上类似 `QGroupBox` 或带有边框的 `QFrame`):
|
|
||||||
|
|
||||||
#### 区域 A: 设备管理器 (上半部分)
|
|
||||||
* 容器: 一个带有边框的容器。
|
|
||||||
* 布局: 水平布局 (`QHBoxLayout`)。
|
|
||||||
* 左侧: 一个 `QLabel` 显示电脑图标。
|
|
||||||
* 中间: 一个 `QLabel` 显示说明文本:"设备管理器列出所有安装在计算机上的硬件设备。请使用设备管理器来更改设备的属性。" (文本需要设置自动换行 `setWordWrap(true)`)。
|
|
||||||
* 右侧/底部: 一个 `QPushButton`,文本为 "设备管理器(D)"。
|
|
||||||
|
|
||||||
#### 区域 B: 设备安装设置 (下半部分)
|
|
||||||
* 容器: 一个带有边框的容器。
|
|
||||||
* 布局: 垂直布局 (`QVBoxLayout`)。
|
|
||||||
* 顶部行: 水平布局 (`QHBoxLayout`)。
|
|
||||||
* 左侧: 一个 `QLabel` 显示列表/勾选图标。
|
|
||||||
* 右侧: 一个 `QLabel` 显示说明文本:"选择 Windows 是否下载制造商提供的可用于你的设备的应用和自定义图标。" (文本需要设置自动换行)。
|
|
||||||
* 底部: 一个 `QPushButton`,文本为 "设备安装设置(S)",靠右对齐。
|
|
||||||
|
|
||||||
### 4. 底部按钮栏
|
|
||||||
* 布局: 水平布局 (`QHBoxLayout`),右对齐 (通常通过 `addStretch()` 实现)。
|
|
||||||
* 组件: 三个 `QPushButton`。
|
|
||||||
* "确定"
|
|
||||||
* "取消"
|
|
||||||
* "应用(A)" (注意:截图中该按钮呈灰色,代码中需设置 `setEnabled(false)`)。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
你制定的计划有一些问题,请按照下述标出的问题一一修正:
|
|
||||||
|
|
||||||
- 编写的代码和说明文件需要使用英文注释。
|
|
||||||
- 是manifest而非manifesto,表示清单文件,请修正这个拼写错误。
|
|
||||||
- 我看到你在mainwindow篇章中编写了大量的C++代码,这没有必要。你是计划者而非执行者。你需要把需要在这个头文件中实现什么?该怎么做?需要使用哪些wfassoc函数,这些函数该怎么调用?在哪里查看他们怎么调用?详细的告诉将要执行这些任务的执行者,而不是直接为他们编写好代码。你在manifest部分的任务规划就非常符合这种范式。
|
|
||||||
|
|
||||||
+153
-2
@@ -1,3 +1,154 @@
|
|||||||
# Q WFAssoc
|
# qwfassoc (suite)
|
||||||
|
|
||||||
TODO
|
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.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,19 @@
|
|||||||
# cmake 模块说明
|
# qwfassoc/cmake
|
||||||
|
|
||||||
此目录下的 `Findwfassoc.cmake` 是从项目根目录 `wfassoc-cdylib/codegen/Findwfassoc.cmake` 复制而来。
|
This directory holds CMake helper modules used by the `qwfassoc` project.
|
||||||
|
|
||||||
该文件提供 `wfassoc::wfassoc` imported target。使用前需要设置 `wfassoc_ROOT` 变量指向 wfassoc 安装目录。
|
## `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,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.
|
||||||
|
|
||||||
|
|||||||
@@ -17,40 +17,108 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Windows File Association C++ API namespace
|
||||||
|
*
|
||||||
|
* Provides C++ RAII wrappers around the C-compatible wfassoc API.
|
||||||
|
*/
|
||||||
namespace wfassocpp {
|
namespace wfassocpp {
|
||||||
|
|
||||||
using wfassoc::CStyleString;
|
/** @brief Type representing a null-terminated UTF-8 C-style string */
|
||||||
using wfassoc::Token;
|
using CString = wfassoc::WFCString;
|
||||||
using wfassoc::HICON;
|
/**
|
||||||
using wfassoc::INVALID_HICON;
|
* @brief Type representing a handle/token for managed objects
|
||||||
using wfassoc::INVALID_INDEX;
|
*
|
||||||
using wfassoc::Scope;
|
* This library uses an object pool to manage any objects created during calling.
|
||||||
using wfassoc::View;
|
* 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
|
/**
|
||||||
|
* @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) {
|
inline void _Check(bool result) {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw std::runtime_error(wfassoc::WFGetLastError());
|
throw std::runtime_error(wfassoc::WFGetLastError());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @private
|
/**
|
||||||
|
* @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() {
|
inline Token _INVALID_TOKEN() {
|
||||||
static Token v = wfassoc::WFInvalidToken();
|
static Token v = wfassoc::WFInvalidToken();
|
||||||
return v;
|
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 {
|
class Schema {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct a new Schema object
|
||||||
|
* @throws std::runtime_error if schema creation fails
|
||||||
|
*/
|
||||||
Schema() { _Check(wfassoc::WFSchemaCreate(&_token)); }
|
Schema() { _Check(wfassoc::WFSchemaCreate(&_token)); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Schema object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the Schema object.
|
||||||
|
*/
|
||||||
~Schema() {
|
~Schema() {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
wfassoc::WFSchemaDestroy(_token);
|
wfassoc::WFSchemaDestroy(_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
Schema(const Schema&) = delete;
|
Schema(const Schema&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
Schema& operator=(const Schema&) = delete;
|
Schema& operator=(const Schema&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
Schema(Schema&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
Schema(Schema&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
Schema& operator=(Schema&& other) noexcept {
|
Schema& operator=(Schema&& other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
@@ -62,41 +130,159 @@ public:
|
|||||||
return *this;
|
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)); }
|
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)); }
|
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)); }
|
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)); }
|
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)); }
|
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)); }
|
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)); }
|
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)); }
|
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)); }
|
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) {
|
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));
|
_Check(wfassoc::WFSchemaAddExt(_token, ext, ext_name, ext_icon, ext_behavior));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Program;
|
friend class Program;
|
||||||
/// @private
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @brief Release ownership of the token
|
||||||
|
*
|
||||||
|
* @return The released token
|
||||||
|
*/
|
||||||
Token Release() noexcept {
|
Token Release() noexcept {
|
||||||
Token t = _token;
|
Token t = _token;
|
||||||
_token = _INVALID_TOKEN();
|
_token = _INVALID_TOKEN();
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
Token _token;
|
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 {
|
class IconRc {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct an IconRc from an existing token
|
||||||
|
*
|
||||||
|
* @param[in] token The icon resource token to wrap
|
||||||
|
*/
|
||||||
explicit IconRc(Token token) : _token(token) {}
|
explicit IconRc(Token token) : _token(token) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the IconRc object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the icon resource.
|
||||||
|
*/
|
||||||
~IconRc() {
|
~IconRc() {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
wfassoc::WFIconRcDestroy(_token);
|
wfassoc::WFIconRcDestroy(_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
IconRc(const IconRc&) = delete;
|
IconRc(const IconRc&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
IconRc& operator=(const IconRc&) = delete;
|
IconRc& operator=(const IconRc&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
IconRc(IconRc&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
IconRc(IconRc&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
IconRc& operator=(IconRc&& other) noexcept {
|
IconRc& operator=(IconRc&& other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
@@ -108,6 +294,13 @@ public:
|
|||||||
return *this;
|
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 GetIcon() {
|
||||||
HICON icon = nullptr;
|
HICON icon = nullptr;
|
||||||
_Check(wfassoc::WFIconRcGetIcon(_token, &icon));
|
_Check(wfassoc::WFIconRcGetIcon(_token, &icon));
|
||||||
@@ -118,17 +311,42 @@ private:
|
|||||||
Token _token;
|
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 {
|
class ExtStatus {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Construct an ExtStatus from an existing token
|
||||||
|
*
|
||||||
|
* @param[in] token The extension status token to wrap
|
||||||
|
*/
|
||||||
explicit ExtStatus(Token token) : _token(token) {}
|
explicit ExtStatus(Token token) : _token(token) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the ExtStatus object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the extension status.
|
||||||
|
*/
|
||||||
~ExtStatus() {
|
~ExtStatus() {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
wfassoc::WFExtStatusDestroy(_token);
|
wfassoc::WFExtStatusDestroy(_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
ExtStatus(const ExtStatus&) = delete;
|
ExtStatus(const ExtStatus&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
ExtStatus& operator=(const ExtStatus&) = delete;
|
ExtStatus& operator=(const ExtStatus&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
ExtStatus(ExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
ExtStatus(ExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
ExtStatus& operator=(ExtStatus&& other) noexcept {
|
ExtStatus& operator=(ExtStatus&& other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
@@ -140,12 +358,30 @@ public:
|
|||||||
return *this;
|
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() {
|
std::string GetName() {
|
||||||
const char* name = nullptr;
|
const char* name = nullptr;
|
||||||
_Check(wfassoc::WFExtStatusGetName(_token, &name));
|
_Check(wfassoc::WFExtStatusGetName(_token, &name));
|
||||||
return std::string(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 GetIcon() {
|
||||||
HICON icon = nullptr;
|
HICON icon = nullptr;
|
||||||
_Check(wfassoc::WFExtStatusGetIcon(_token, &icon));
|
_Check(wfassoc::WFExtStatusGetIcon(_token, &icon));
|
||||||
@@ -156,17 +392,42 @@ private:
|
|||||||
Token _token;
|
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 {
|
class SelfExtStatus {
|
||||||
public:
|
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) {}
|
explicit SelfExtStatus(Token token) : _token(token) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the SelfExtStatus object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the self extension status.
|
||||||
|
*/
|
||||||
~SelfExtStatus() {
|
~SelfExtStatus() {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
wfassoc::WFSelfExtStatusDestroy(_token);
|
wfassoc::WFSelfExtStatusDestroy(_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
SelfExtStatus(const SelfExtStatus&) = delete;
|
SelfExtStatus(const SelfExtStatus&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
SelfExtStatus& operator=(const SelfExtStatus&) = delete;
|
SelfExtStatus& operator=(const SelfExtStatus&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
SelfExtStatus(SelfExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
SelfExtStatus(SelfExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
SelfExtStatus& operator=(SelfExtStatus&& other) noexcept {
|
SelfExtStatus& operator=(SelfExtStatus&& other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
@@ -178,24 +439,53 @@ public:
|
|||||||
return *this;
|
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() {
|
std::string GetName() {
|
||||||
const char* name = nullptr;
|
const char* name = nullptr;
|
||||||
_Check(wfassoc::WFSelfExtStatusGetName(_token, &name));
|
_Check(wfassoc::WFSelfExtStatusGetName(_token, &name));
|
||||||
return std::string(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 GetIcon() {
|
||||||
HICON icon = nullptr;
|
HICON icon = nullptr;
|
||||||
_Check(wfassoc::WFSelfExtStatusGetIcon(_token, &icon));
|
_Check(wfassoc::WFSelfExtStatusGetIcon(_token, &icon));
|
||||||
return 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() {
|
std::string GetExt() {
|
||||||
const char* inner = nullptr;
|
const char* inner = nullptr;
|
||||||
_Check(wfassoc::WFSelfExtStatusGetExt(_token, &inner));
|
_Check(wfassoc::WFSelfExtStatusGetExt(_token, &inner));
|
||||||
return std::string(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() {
|
std::string GetDottedExt() {
|
||||||
const char* inner = nullptr;
|
const char* inner = nullptr;
|
||||||
_Check(wfassoc::WFSelfExtStatusGetDottedExt(_token, &inner));
|
_Check(wfassoc::WFSelfExtStatusGetDottedExt(_token, &inner));
|
||||||
@@ -206,19 +496,48 @@ private:
|
|||||||
Token _token;
|
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 {
|
class Program {
|
||||||
public:
|
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) {
|
explicit Program(Schema&& schema) {
|
||||||
_Check(wfassoc::WFProgramCreate(schema.Release(), &_token));
|
_Check(wfassoc::WFProgramCreate(schema.Release(), &_token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroy the Program object
|
||||||
|
*
|
||||||
|
* Releases resources associated with the Program.
|
||||||
|
*/
|
||||||
~Program() {
|
~Program() {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
wfassoc::WFProgramDestroy(_token);
|
wfassoc::WFProgramDestroy(_token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @brief Non-copyable */
|
||||||
Program(const Program&) = delete;
|
Program(const Program&) = delete;
|
||||||
|
/** @brief Non-copyable */
|
||||||
Program& operator=(const Program&) = delete;
|
Program& operator=(const Program&) = delete;
|
||||||
|
|
||||||
|
/** @brief Move constructor */
|
||||||
Program(Program&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
Program(Program&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
|
||||||
|
/** @brief Move assignment operator */
|
||||||
Program& operator=(Program&& other) noexcept {
|
Program& operator=(Program&& other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
if (_token != _INVALID_TOKEN()) {
|
if (_token != _INVALID_TOKEN()) {
|
||||||
@@ -230,48 +549,132 @@ public:
|
|||||||
return *this;
|
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() {
|
std::string ResolveName() {
|
||||||
const char* name = nullptr;
|
const char* name = nullptr;
|
||||||
_Check(wfassoc::WFProgramResolveName(_token, &name));
|
_Check(wfassoc::WFProgramResolveName(_token, &name));
|
||||||
return std::string(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() {
|
IconRc ResolveIcon() {
|
||||||
Token token = _INVALID_TOKEN();
|
Token token = _INVALID_TOKEN();
|
||||||
_Check(wfassoc::WFProgramResolveIcon(_token, &token));
|
_Check(wfassoc::WFProgramResolveIcon(_token, &token));
|
||||||
return IconRc(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 ExtsLen() {
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
_Check(wfassoc::WFProgramExtsLen(_token, &len));
|
_Check(wfassoc::WFProgramExtsLen(_token, &len));
|
||||||
return 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 FindExt(const char* body) {
|
||||||
size_t index = INVALID_INDEX;
|
size_t index = INVALID_INDEX;
|
||||||
_Check(wfassoc::WFProgramFindExt(_token, body, &index));
|
_Check(wfassoc::WFProgramFindExt(_token, body, &index));
|
||||||
return 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) {
|
SelfExtStatus ResolveExt(size_t index) {
|
||||||
Token token = _INVALID_TOKEN();
|
Token token = _INVALID_TOKEN();
|
||||||
_Check(wfassoc::WFProgramResolveExt(_token, index, &token));
|
_Check(wfassoc::WFProgramResolveExt(_token, index, &token));
|
||||||
return SelfExtStatus(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)); }
|
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)); }
|
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 IsRegistered(Scope scope) {
|
||||||
bool result = false;
|
bool result = false;
|
||||||
_Check(wfassoc::WFProgramIsRegistered(_token, scope, &result));
|
_Check(wfassoc::WFProgramIsRegistered(_token, scope, &result));
|
||||||
return 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)); }
|
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)); }
|
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) {
|
std::optional<ExtStatus> QueryExt(View view, size_t index) {
|
||||||
Token token = _INVALID_TOKEN();
|
Token token = _INVALID_TOKEN();
|
||||||
_Check(wfassoc::WFProgramQueryExt(_token, view, index, &token));
|
_Check(wfassoc::WFProgramQueryExt(_token, view, index, &token));
|
||||||
|
|||||||
@@ -22,41 +22,47 @@
|
|||||||
|
|
||||||
|
|
||||||
#ifdef __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 {
|
namespace wfassoc {
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
/** Type representing a null-terminated UTF-8 C-style string */
|
/** Type representing a null-terminated UTF-8 C-style string */
|
||||||
using CStyleString = const char*;
|
using WFCString = const char*;
|
||||||
/**
|
/**
|
||||||
* @brief Type representing a handle/token for managed objects
|
* @brief Type representing a handle/token for managed objects
|
||||||
*
|
*
|
||||||
* This library use object pool to manage any objects created during calling.
|
* 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.
|
* And we expose this type as an opaque handle for visiting your created object.
|
||||||
*/
|
*/
|
||||||
using Token = uint64_t;
|
using WFToken = uint64_t;
|
||||||
/**
|
/**
|
||||||
* @brief Type representing an icon handle (opaque pointer)
|
* @brief Type representing an icon handle (opaque pointer)
|
||||||
*
|
*
|
||||||
* This type is equivalent with Win32 HICON type.
|
* This type is equivalent with Win32 HICON type.
|
||||||
*/
|
*/
|
||||||
using HICON = void*;
|
using WFHICON = void*;
|
||||||
#else // __cplusplus
|
#else // __cplusplus
|
||||||
typedef const char *CStyleString;
|
typedef const char *WFCString;
|
||||||
typedef uint64_t Token;
|
typedef uint64_t WFToken;
|
||||||
typedef void *HICON;
|
typedef void *WFHICON;
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
/** Invalid icon handle value */
|
/** Invalid icon handle value */
|
||||||
constexpr HICON INVALID_HICON = nullptr;
|
constexpr WFHICON WF_INVALID_HICON = nullptr;
|
||||||
/** Invalid index value used for error conditions */
|
/** Invalid index value used for error conditions */
|
||||||
constexpr size_t INVALID_INDEX = static_cast<size_t>(-1);
|
constexpr size_t WF_INVALID_INDEX = static_cast<size_t>(-1);
|
||||||
#else // __cplusplus
|
#else // __cplusplus
|
||||||
static const HICON INVALID_HICON = NULL;
|
static const WFHICON WF_INVALID_HICON = NULL;
|
||||||
static const size_t INVALID_INDEX = ((size_t)-1);
|
static const size_t WF_INVALID_INDEX = ((size_t)-1);
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +72,7 @@ static const size_t INVALID_INDEX = ((size_t)-1);
|
|||||||
*
|
*
|
||||||
* Determines whether a program is registered for the current user or system-wide.
|
* Determines whether a program is registered for the current user or system-wide.
|
||||||
*/
|
*/
|
||||||
enum class Scope : uint32_t {
|
enum class WFScope : uint32_t {
|
||||||
/** Current user scope */
|
/** Current user scope */
|
||||||
User = 0u,
|
User = 0u,
|
||||||
/** System-wide scope */
|
/** System-wide scope */
|
||||||
@@ -77,7 +83,7 @@ enum class Scope : uint32_t {
|
|||||||
*
|
*
|
||||||
* Determines how the association status is viewed/queried.
|
* Determines how the association status is viewed/queried.
|
||||||
*/
|
*/
|
||||||
enum class View : uint32_t {
|
enum class WFView : uint32_t {
|
||||||
/** User-level view */
|
/** User-level view */
|
||||||
User = 0u,
|
User = 0u,
|
||||||
/** System-level view */
|
/** System-level view */
|
||||||
@@ -86,18 +92,18 @@ enum class View : uint32_t {
|
|||||||
Hybrid = 2u
|
Hybrid = 2u
|
||||||
};
|
};
|
||||||
#else // __cplusplus
|
#else // __cplusplus
|
||||||
typedef uint32_t Scope;
|
typedef uint32_t WFScope;
|
||||||
/** Current user scope */
|
/** Current user scope */
|
||||||
static const Scope SCOPE_USER = 0u;
|
static const WFScope WF_SCOPE_USER = 0u;
|
||||||
/** System-wide scope */
|
/** System-wide scope */
|
||||||
static const Scope SCOPE_SYSTEM = 1u;
|
static const WFScope WF_SCOPE_SYSTEM = 1u;
|
||||||
typedef uint32_t View;
|
typedef uint32_t WFView;
|
||||||
/** User-level view */
|
/** User-level view */
|
||||||
static const View VIEW_USER = 0u;
|
static const WFView WF_VIEW_USER = 0u;
|
||||||
/** System-level view */
|
/** System-level view */
|
||||||
static const View VIEW_SYSTEM = 1u;
|
static const WFView WF_VIEW_SYSTEM = 1u;
|
||||||
/** Combined hybrid view of both user and system */
|
/** Combined hybrid view of both user and system */
|
||||||
static const View VIEW_HYBRID = 2u;
|
static const WFView WF_VIEW_HYBRID = 2u;
|
||||||
#endif // __cplusplus
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
|
||||||
@@ -143,7 +149,7 @@ bool WFShutdown(void);
|
|||||||
* If no error has occurred, the string is empty.
|
* If no error has occurred, the string is empty.
|
||||||
* There is no possibility of a NULL return value.
|
* There is no possibility of a NULL return value.
|
||||||
*/
|
*/
|
||||||
CStyleString WFGetLastError(void);
|
WFCString WFGetLastError(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if the current process has administrative privileges
|
* @brief Check if the current process has administrative privileges
|
||||||
@@ -170,7 +176,7 @@ bool WFHasPrivilege(void);
|
|||||||
*
|
*
|
||||||
* @return An invalid token value
|
* @return An invalid token value
|
||||||
*/
|
*/
|
||||||
Token WFInvalidToken(void);
|
WFToken WFInvalidWFToken(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a new Schema object
|
* @brief Create a new Schema object
|
||||||
@@ -185,7 +191,7 @@ Token WFInvalidToken(void);
|
|||||||
* or consumed by creating a Program object via WFProgramCreate().
|
* or consumed by creating a Program object via WFProgramCreate().
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaCreate(Token *out_schema);
|
bool WFSchemaCreate(WFToken *out_schema);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destroy a Schema object
|
* @brief Destroy a Schema object
|
||||||
@@ -198,7 +204,7 @@ bool WFSchemaCreate(Token *out_schema);
|
|||||||
* @param[in] in_schema Schema token to destroy
|
* @param[in] in_schema Schema token to destroy
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaDestroy(Token in_schema);
|
bool WFSchemaDestroy(WFToken in_schema);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the program identifier for a Schema
|
* @brief Set the program identifier for a Schema
|
||||||
@@ -209,7 +215,7 @@ bool WFSchemaDestroy(Token in_schema);
|
|||||||
* and follow with alphabet characters, digits, underline, or hyphens.
|
* and follow with alphabet characters, digits, underline, or hyphens.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaSetIdentifier(Token in_schema, CStyleString in_value);
|
bool WFSchemaSetIdentifier(WFToken in_schema, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the program path for a Schema
|
* @brief Set the program path for a Schema
|
||||||
@@ -219,7 +225,7 @@ bool WFSchemaSetIdentifier(Token in_schema, CStyleString in_value);
|
|||||||
* This path should be the fully qualified path to the application.
|
* This path should be the fully qualified path to the application.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaSetPath(Token in_schema, CStyleString in_value);
|
bool WFSchemaSetPath(WFToken in_schema, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the program CLSID for a Schema
|
* @brief Set the program CLSID for a Schema
|
||||||
@@ -230,7 +236,7 @@ bool WFSchemaSetPath(Token in_schema, CStyleString in_value);
|
|||||||
* Please note that curly braces are required.
|
* Please note that curly braces are required.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaSetClsid(Token in_schema, CStyleString in_value);
|
bool WFSchemaSetClsid(WFToken in_schema, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the program name for a Schema (optional)
|
* @brief Set the program name for a Schema (optional)
|
||||||
@@ -239,7 +245,7 @@ bool WFSchemaSetClsid(Token in_schema, CStyleString in_value);
|
|||||||
* @param[in] in_value Null-terminated UTF-8 string containing the name, or NULL to clear
|
* @param[in] in_value Null-terminated UTF-8 string containing the name, or NULL to clear
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaSetName(Token in_schema, CStyleString in_value);
|
bool WFSchemaSetName(WFToken in_schema, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the program icon for a Schema (optional)
|
* @brief Set the program icon for a Schema (optional)
|
||||||
@@ -248,7 +254,7 @@ bool WFSchemaSetName(Token in_schema, CStyleString in_value);
|
|||||||
* @param[in] in_value Null-terminated UTF-8 string containing the icon path, or NULL to clear
|
* @param[in] in_value Null-terminated UTF-8 string containing the icon path, or NULL to clear
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaSetIcon(Token in_schema, CStyleString in_value);
|
bool WFSchemaSetIcon(WFToken in_schema, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the program behavior for a Schema (optional)
|
* @brief Set the program behavior for a Schema (optional)
|
||||||
@@ -257,7 +263,7 @@ bool WFSchemaSetIcon(Token in_schema, CStyleString in_value);
|
|||||||
* @param[in] in_value Null-terminated UTF-8 string containing the behavior command, or NULL to clear
|
* @param[in] in_value Null-terminated UTF-8 string containing the behavior command, or NULL to clear
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaSetBehavior(Token in_schema, CStyleString in_value);
|
bool WFSchemaSetBehavior(WFToken in_schema, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add a string resource entry to a Schema
|
* @brief Add a string resource entry to a Schema
|
||||||
@@ -268,7 +274,7 @@ bool WFSchemaSetBehavior(Token in_schema, CStyleString in_value);
|
|||||||
* It can be a plain string or a reference string to resource.
|
* It can be a plain string or a reference string to resource.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaAddStr(Token in_schema, CStyleString in_name, CStyleString in_value);
|
bool WFSchemaAddStr(WFToken in_schema, WFCString in_name, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add an icon registry entry to a Schema
|
* @brief Add an icon registry entry to a Schema
|
||||||
@@ -279,7 +285,7 @@ bool WFSchemaAddStr(Token in_schema, CStyleString in_name, CStyleString in_value
|
|||||||
* It can be a path to icon or a reference string to resource.
|
* It can be a path to icon or a reference string to resource.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaAddIcon(Token in_schema, CStyleString in_name, CStyleString in_value);
|
bool WFSchemaAddIcon(WFToken in_schema, WFCString in_name, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add a behavior registry entry to a Schema
|
* @brief Add a behavior registry entry to a Schema
|
||||||
@@ -290,7 +296,7 @@ bool WFSchemaAddIcon(Token in_schema, CStyleString in_name, CStyleString in_valu
|
|||||||
* It should be a valid command line string which use \c %1, \c %2, etc. to represent parameters.
|
* 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
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaAddBehavior(Token in_schema, CStyleString in_name, CStyleString in_value);
|
bool WFSchemaAddBehavior(WFToken in_schema, WFCString in_name, WFCString in_value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add a file extension to a Schema
|
* @brief Add a file extension to a Schema
|
||||||
@@ -305,11 +311,11 @@ bool WFSchemaAddBehavior(Token in_schema, CStyleString in_name, CStyleString in_
|
|||||||
* This name should be registered by calling WFSchemaAddBehavior().
|
* This name should be registered by calling WFSchemaAddBehavior().
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSchemaAddExt(Token in_schema,
|
bool WFSchemaAddExt(WFToken in_schema,
|
||||||
CStyleString in_ext,
|
WFCString in_ext,
|
||||||
CStyleString in_ext_name,
|
WFCString in_ext_name,
|
||||||
CStyleString in_ext_icon,
|
WFCString in_ext_icon,
|
||||||
CStyleString in_ext_behavior);
|
WFCString in_ext_behavior);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a Program object from a Schema
|
* @brief Create a Program object from a Schema
|
||||||
@@ -327,7 +333,7 @@ bool WFSchemaAddExt(Token in_schema,
|
|||||||
* And it should be freed by calling WFProgramDestroy() when it is no longer needed.
|
* And it should be freed by calling WFProgramDestroy() when it is no longer needed.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramCreate(Token in_schema, Token *out_program);
|
bool WFProgramCreate(WFToken in_schema, WFToken *out_program);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destroy a Program object
|
* @brief Destroy a Program object
|
||||||
@@ -337,7 +343,7 @@ bool WFProgramCreate(Token in_schema, Token *out_program);
|
|||||||
* @param[in] in_program Program token to destroy
|
* @param[in] in_program Program token to destroy
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramDestroy(Token in_program);
|
bool WFProgramDestroy(WFToken in_program);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Resolve the provided program name of this Program
|
* @brief Resolve the provided program name of this Program
|
||||||
@@ -352,7 +358,7 @@ bool WFProgramDestroy(Token in_program);
|
|||||||
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
* 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
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramResolveName(Token in_program, CStyleString *out_name);
|
bool WFProgramResolveName(WFToken in_program, WFCString *out_name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Resolve the Program icon resource
|
* @brief Resolve the Program icon resource
|
||||||
@@ -367,7 +373,7 @@ bool WFProgramResolveName(Token in_program, CStyleString *out_name);
|
|||||||
* And it should be freed by calling WFIconRcDestroy() when it is no longer needed.
|
* And it should be freed by calling WFIconRcDestroy() when it is no longer needed.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramResolveIcon(Token in_program, Token *out_icon_rc);
|
bool WFProgramResolveIcon(WFToken in_program, WFToken *out_icon_rc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the number of file extensions in the Program
|
* @brief Get the number of file extensions in the Program
|
||||||
@@ -376,17 +382,17 @@ bool WFProgramResolveIcon(Token in_program, Token *out_icon_rc);
|
|||||||
* @param[out] out_len Pointer to receive the number of extensions
|
* @param[out] out_len Pointer to receive the number of extensions
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramExtsLen(Token in_program, size_t *out_len);
|
bool WFProgramExtsLen(WFToken in_program, size_t *out_len);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Find a file extension by its body (extension string)
|
* @brief Find a file extension by its body (extension string)
|
||||||
*
|
*
|
||||||
* @param[in] in_program Program token
|
* @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[in] in_body Null-terminated UTF-8 string containing the file extension name (without leading dot) to find.
|
||||||
* @param[out] out_index Pointer to receive the file extension index, or INVALID_INDEX if not found.
|
* @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
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramFindExt(Token in_program, CStyleString in_body, size_t *out_index);
|
bool WFProgramFindExt(WFToken in_program, WFCString in_body, size_t *out_index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Resolve this program provided extension's details by index
|
* @brief Resolve this program provided extension's details by index
|
||||||
@@ -398,7 +404,7 @@ bool WFProgramFindExt(Token in_program, CStyleString in_body, size_t *out_index)
|
|||||||
* And it should be freed by calling WFSelfExtStatusDestroy() when it is no longer needed.
|
* And it should be freed by calling WFSelfExtStatusDestroy() when it is no longer needed.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramResolveExt(Token in_program, size_t in_index, Token *out_self_ext_status);
|
bool WFProgramResolveExt(WFToken in_program, size_t in_index, WFToken *out_self_ext_status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Register the Program in the specified scope
|
* @brief Register the Program in the specified scope
|
||||||
@@ -407,7 +413,7 @@ bool WFProgramResolveExt(Token in_program, size_t in_index, Token *out_self_ext_
|
|||||||
* @param[in] in_scope Registration scope
|
* @param[in] in_scope Registration scope
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramRegister(Token in_program, Scope in_scope);
|
bool WFProgramRegister(WFToken in_program, WFScope in_scope);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Unregister the Program from the specified scope
|
* @brief Unregister the Program from the specified scope
|
||||||
@@ -416,7 +422,7 @@ bool WFProgramRegister(Token in_program, Scope in_scope);
|
|||||||
* @param[in] in_scope Registration scope
|
* @param[in] in_scope Registration scope
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramUnregister(Token in_program, Scope in_scope);
|
bool WFProgramUnregister(WFToken in_program, WFScope in_scope);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Check if the Program is registered in the specified scope
|
* @brief Check if the Program is registered in the specified scope
|
||||||
@@ -427,7 +433,7 @@ bool WFProgramUnregister(Token in_program, Scope in_scope);
|
|||||||
* True if the Program is registered in the specified scope, false otherwise.
|
* True if the Program is registered in the specified scope, false otherwise.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramIsRegistered(Token in_program, Scope in_scope, bool *out_is_registered);
|
bool WFProgramIsRegistered(WFToken in_program, WFScope in_scope, bool *out_is_registered);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Link a file extension in the specified scope
|
* @brief Link a file extension in the specified scope
|
||||||
@@ -437,7 +443,7 @@ bool WFProgramIsRegistered(Token in_program, Scope in_scope, bool *out_is_regist
|
|||||||
* @param[in] in_index Index of the extension to link
|
* @param[in] in_index Index of the extension to link
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramLinkExt(Token in_program, Scope in_scope, size_t in_index);
|
bool WFProgramLinkExt(WFToken in_program, WFScope in_scope, size_t in_index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Unlink a file extension in the specified scope
|
* @brief Unlink a file extension in the specified scope
|
||||||
@@ -447,7 +453,7 @@ bool WFProgramLinkExt(Token in_program, Scope in_scope, size_t in_index);
|
|||||||
* @param[in] in_index Index of the extension to unlink
|
* @param[in] in_index Index of the extension to unlink
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramUnlinkExt(Token in_program, Scope in_scope, size_t in_index);
|
bool WFProgramUnlinkExt(WFToken in_program, WFScope in_scope, size_t in_index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Query the status of a file extension
|
* @brief Query the status of a file extension
|
||||||
@@ -461,7 +467,7 @@ bool WFProgramUnlinkExt(Token in_program, Scope in_scope, size_t in_index);
|
|||||||
* And it should be freed by calling WFExtStatusDestroy() when it is no longer needed.
|
* And it should be freed by calling WFExtStatusDestroy() when it is no longer needed.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFProgramQueryExt(Token in_program, View in_view, size_t in_index, Token *out_ext_status);
|
bool WFProgramQueryExt(WFToken in_program, WFView in_view, size_t in_index, WFToken *out_ext_status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destroy an extension status object
|
* @brief Destroy an extension status object
|
||||||
@@ -469,7 +475,7 @@ bool WFProgramQueryExt(Token in_program, View in_view, size_t in_index, Token *o
|
|||||||
* @param[in] in_ext_status Extension status token to destroy
|
* @param[in] in_ext_status Extension status token to destroy
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFExtStatusDestroy(Token in_ext_status);
|
bool WFExtStatusDestroy(WFToken in_ext_status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the display name from an extension status object
|
* @brief Get the display name from an extension status object
|
||||||
@@ -484,7 +490,7 @@ bool WFExtStatusDestroy(Token in_ext_status);
|
|||||||
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
* 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
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFExtStatusGetName(Token in_ext_status, CStyleString *out_name);
|
bool WFExtStatusGetName(WFToken in_ext_status, WFCString *out_name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the icon from an extension status object
|
* @brief Get the icon from an extension status object
|
||||||
@@ -498,7 +504,7 @@ bool WFExtStatusGetName(Token in_ext_status, CStyleString *out_name);
|
|||||||
* Please make a copy immediately if you need to use it longer.
|
* Please make a copy immediately if you need to use it longer.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFExtStatusGetIcon(Token in_ext_status, HICON *out_icon);
|
bool WFExtStatusGetIcon(WFToken in_ext_status, WFHICON *out_icon);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destroy a self extension status object
|
* @brief Destroy a self extension status object
|
||||||
@@ -506,7 +512,7 @@ bool WFExtStatusGetIcon(Token in_ext_status, HICON *out_icon);
|
|||||||
* @param[in] in_self_ext_status Self extension status token to destroy
|
* @param[in] in_self_ext_status Self extension status token to destroy
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSelfExtStatusDestroy(Token in_self_ext_status);
|
bool WFSelfExtStatusDestroy(WFToken in_self_ext_status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the display name from a self extension status object
|
* @brief Get the display name from a self extension status object
|
||||||
@@ -520,7 +526,7 @@ bool WFSelfExtStatusDestroy(Token in_self_ext_status);
|
|||||||
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
* 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
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSelfExtStatusGetName(Token in_self_ext_status, CStyleString *out_name);
|
bool WFSelfExtStatusGetName(WFToken in_self_ext_status, WFCString *out_name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the icon from a self extension status object
|
* @brief Get the icon from a self extension status object
|
||||||
@@ -534,7 +540,7 @@ bool WFSelfExtStatusGetName(Token in_self_ext_status, CStyleString *out_name);
|
|||||||
* Please make a copy immediately if you need to use it longer.
|
* Please make a copy immediately if you need to use it longer.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSelfExtStatusGetIcon(Token in_self_ext_status, HICON *out_icon);
|
bool WFSelfExtStatusGetIcon(WFToken in_self_ext_status, WFHICON *out_icon);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the extension string (without leading dot) from a self extension status object
|
* @brief Get the extension string (without leading dot) from a self extension status object
|
||||||
@@ -545,7 +551,7 @@ bool WFSelfExtStatusGetIcon(Token in_self_ext_status, HICON *out_icon);
|
|||||||
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
* 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
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSelfExtStatusGetExt(Token in_self_ext_status, CStyleString *out_inner);
|
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
|
* @brief Get the dotted extension string (with leading dot) from a self extension status object
|
||||||
@@ -556,7 +562,7 @@ bool WFSelfExtStatusGetExt(Token in_self_ext_status, CStyleString *out_inner);
|
|||||||
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
|
* 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
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFSelfExtStatusGetDottedExt(Token in_self_ext_status, CStyleString *out_inner);
|
bool WFSelfExtStatusGetDottedExt(WFToken in_self_ext_status, WFCString *out_inner);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Destroy an icon resource object
|
* @brief Destroy an icon resource object
|
||||||
@@ -564,19 +570,19 @@ bool WFSelfExtStatusGetDottedExt(Token in_self_ext_status, CStyleString *out_inn
|
|||||||
* @param[in] in_icon_rc Icon resource token to destroy
|
* @param[in] in_icon_rc Icon resource token to destroy
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFIconRcDestroy(Token in_icon_rc);
|
bool WFIconRcDestroy(WFToken in_icon_rc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the icon handle from an icon resource object
|
* @brief Get the icon handle from an icon resource object
|
||||||
*
|
*
|
||||||
* @param[in] in_icon_rc Icon resource token
|
* @param[in] in_icon_rc Icon resource token
|
||||||
* @param[out] out_icon Pointer to receive the icon handle.
|
* @param[out] out_icon Pointer to receive the icon handle.
|
||||||
* There is no possibility that this value is INVALID_HICON.
|
* There is no possibility that this value is WF_INVALID_HICON.
|
||||||
* This icon handle will be freed once this icon resource object is destroyed.
|
* 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.
|
* Please make a copy immediately if you need to use it longer.
|
||||||
* @return true on success, false on failure
|
* @return true on success, false on failure
|
||||||
*/
|
*/
|
||||||
bool WFIconRcGetIcon(Token in_icon_rc, HICON *out_icon);
|
bool WFIconRcGetIcon(WFToken in_icon_rc, WFHICON *out_icon);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+121
-119
@@ -14,17 +14,17 @@ 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("C-Style string FFI 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("object pool error: {0}")]
|
#[error("object pool error: {0}")]
|
||||||
@@ -202,9 +202,11 @@ static ICON_RC_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::IconRc>
|
|||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
@@ -244,7 +246,7 @@ pub extern "C" fn WFShutdown() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,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)?)
|
||||||
})
|
})
|
||||||
@@ -280,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)?);
|
||||||
@@ -293,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)?);
|
||||||
@@ -306,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)?);
|
||||||
@@ -319,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)?;
|
||||||
|
|
||||||
@@ -338,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)?;
|
||||||
|
|
||||||
@@ -357,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)?;
|
||||||
|
|
||||||
@@ -376,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(
|
||||||
@@ -395,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(
|
||||||
@@ -414,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(
|
||||||
@@ -433,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(
|
||||||
@@ -462,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)?;
|
||||||
|
|
||||||
@@ -476,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)?)
|
||||||
})
|
})
|
||||||
@@ -485,10 +487,10 @@ 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)?;
|
||||||
|
|
||||||
@@ -500,10 +502,10 @@ pub extern "C" fn WFProgramResolveName(
|
|||||||
|
|
||||||
#[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)?;
|
||||||
|
|
||||||
@@ -515,10 +517,10 @@ pub extern "C" fn WFProgramResolveIcon(
|
|||||||
|
|
||||||
#[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())
|
||||||
@@ -527,18 +529,18 @@ pub extern "C" fn WFProgramExtsLen(
|
|||||||
|
|
||||||
#[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)
|
||||||
})
|
})
|
||||||
@@ -546,11 +548,11 @@ pub extern "C" fn WFProgramFindExt(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramResolveExt(
|
pub extern "C" fn WFProgramResolveExt(
|
||||||
in_program: in_param_ty!(Token),
|
in_program: in_param_ty!(WFToken),
|
||||||
in_index: in_param_ty!(usize),
|
in_index: in_param_ty!(usize),
|
||||||
out_self_ext_status: out_param_ty!(Token),
|
out_self_ext_status: out_param_ty!(WFToken),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_program: Token, in_index: usize| -> (out_self_ext_status: Token) {
|
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)?;
|
||||||
|
|
||||||
@@ -563,13 +565,13 @@ pub extern "C" fn WFProgramResolveExt(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFProgramRegister(
|
pub extern "C" fn WFProgramRegister(
|
||||||
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.register(scope.into())?;
|
program.register(scope.into())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@@ -577,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(())
|
||||||
})
|
})
|
||||||
@@ -591,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(())
|
||||||
})
|
})
|
||||||
@@ -620,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(())
|
||||||
})
|
})
|
||||||
@@ -635,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 {
|
||||||
@@ -662,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)?)
|
||||||
})
|
})
|
||||||
@@ -671,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)?;
|
||||||
|
|
||||||
@@ -685,10 +687,10 @@ 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)?;
|
||||||
|
|
||||||
@@ -702,8 +704,8 @@ pub extern "C" fn WFExtStatusGetIcon(
|
|||||||
// region: Self Extension Status
|
// region: Self Extension Status
|
||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSelfExtStatusDestroy(in_self_ext_status: in_param_ty!(Token)) -> bool {
|
pub extern "C" fn WFSelfExtStatusDestroy(in_self_ext_status: in_param_ty!(WFToken)) -> bool {
|
||||||
cffi_wrapper!(|in_self_ext_status: Token| {
|
cffi_wrapper!(|in_self_ext_status: WFToken| {
|
||||||
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
|
||||||
Ok(pool.free(in_self_ext_status)?)
|
Ok(pool.free(in_self_ext_status)?)
|
||||||
})
|
})
|
||||||
@@ -711,10 +713,10 @@ pub extern "C" fn WFSelfExtStatusDestroy(in_self_ext_status: in_param_ty!(Token)
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSelfExtStatusGetName(
|
pub extern "C" fn WFSelfExtStatusGetName(
|
||||||
in_self_ext_status: in_param_ty!(Token),
|
in_self_ext_status: in_param_ty!(WFToken),
|
||||||
out_name: out_param_ty!(CStyleString),
|
out_name: out_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_self_ext_status: Token| -> (out_name: CStyleString) {
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_name: WFCString) {
|
||||||
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
let self_ext_status = pool.get(in_self_ext_status)?;
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
@@ -725,10 +727,10 @@ pub extern "C" fn WFSelfExtStatusGetName(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSelfExtStatusGetIcon(
|
pub extern "C" fn WFSelfExtStatusGetIcon(
|
||||||
in_self_ext_status: in_param_ty!(Token),
|
in_self_ext_status: in_param_ty!(WFToken),
|
||||||
out_icon: out_param_ty!(HICON),
|
out_icon: out_param_ty!(WFHICON),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_self_ext_status: Token| -> (out_icon: HICON) {
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_icon: WFHICON) {
|
||||||
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
let self_ext_status = pool.get(in_self_ext_status)?;
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
@@ -739,10 +741,10 @@ pub extern "C" fn WFSelfExtStatusGetIcon(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSelfExtStatusGetExt(
|
pub extern "C" fn WFSelfExtStatusGetExt(
|
||||||
in_self_ext_status: in_param_ty!(Token),
|
in_self_ext_status: in_param_ty!(WFToken),
|
||||||
out_inner: out_param_ty!(CStyleString),
|
out_inner: out_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_self_ext_status: Token| -> (out_inner: CStyleString) {
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_inner: WFCString) {
|
||||||
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
let self_ext_status = pool.get(in_self_ext_status)?;
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
@@ -753,10 +755,10 @@ pub extern "C" fn WFSelfExtStatusGetExt(
|
|||||||
|
|
||||||
#[unsafe(no_mangle)]
|
#[unsafe(no_mangle)]
|
||||||
pub extern "C" fn WFSelfExtStatusGetDottedExt(
|
pub extern "C" fn WFSelfExtStatusGetDottedExt(
|
||||||
in_self_ext_status: in_param_ty!(Token),
|
in_self_ext_status: in_param_ty!(WFToken),
|
||||||
out_inner: out_param_ty!(CStyleString),
|
out_inner: out_param_ty!(WFCString),
|
||||||
) -> bool {
|
) -> bool {
|
||||||
cffi_wrapper!(|in_self_ext_status: Token| -> (out_inner: CStyleString) {
|
cffi_wrapper!(|in_self_ext_status: WFToken| -> (out_inner: WFCString) {
|
||||||
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
|
||||||
let self_ext_status = pool.get(in_self_ext_status)?;
|
let self_ext_status = pool.get(in_self_ext_status)?;
|
||||||
|
|
||||||
@@ -770,8 +772,8 @@ pub extern "C" fn WFSelfExtStatusGetDottedExt(
|
|||||||
// 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)?)
|
||||||
})
|
})
|
||||||
@@ -779,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)?;
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-11
@@ -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,7 +125,7 @@ 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.");
|
||||||
|
|||||||
+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
|
||||||
|
|||||||
@@ -17,37 +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("{0}")]
|
#[error("Program operation error: {0}")]
|
||||||
LoadIconRc(#[from] concept::LoadIconRcError),
|
LoadIconRc(#[from] concept::LoadIconRcError),
|
||||||
#[error("given index is invalid")]
|
#[error("given file extension index {0} is invalid when visiting Program")]
|
||||||
BadIndex,
|
BadIndex(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
@@ -62,6 +63,11 @@ pub struct Program {
|
|||||||
app_path: String,
|
app_path: String,
|
||||||
app_file_name: 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>>,
|
||||||
@@ -103,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,
|
||||||
@@ -119,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>>,
|
||||||
@@ -129,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())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,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| {
|
||||||
@@ -260,6 +273,7 @@ impl Program {
|
|||||||
app_path,
|
app_path,
|
||||||
app_file_name,
|
app_file_name,
|
||||||
app_dir_path,
|
app_dir_path,
|
||||||
|
clsid,
|
||||||
name,
|
name,
|
||||||
icon,
|
icon,
|
||||||
behavior,
|
behavior,
|
||||||
@@ -273,6 +287,10 @@ impl Program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
|
/// Resolve the display name of this application.
|
||||||
|
///
|
||||||
|
/// 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> {
|
pub fn resolve_name(&self) -> Result<String, ProgramError> {
|
||||||
// Fecch from user specified name first
|
// Fecch from user specified name first
|
||||||
let name = self
|
let name = self
|
||||||
@@ -291,6 +309,10 @@ impl Program {
|
|||||||
Ok(self.app_file_name.clone())
|
Ok(self.app_file_name.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve the icon of this application.
|
||||||
|
///
|
||||||
|
/// 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> {
|
pub fn resolve_icon(&self) -> Result<concept::IconRc, ProgramError> {
|
||||||
// Fetch from user specified icon first
|
// Fetch from user specified icon first
|
||||||
let icon = self
|
let icon = self
|
||||||
@@ -312,17 +334,29 @@ impl Program {
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Find the index of a file extension by its body (without leading dot).
|
||||||
|
///
|
||||||
|
/// Returns `None` if the given extension is not associated with this program.
|
||||||
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> {
|
pub fn resolve_ext(&self, index: usize) -> Result<ProgramSelfExtStatus, ProgramError> {
|
||||||
// Fetch data
|
// Fetch data
|
||||||
let progid_ext_key = self.ext_keys.get(index).ok_or(ProgramError::BadIndex)?;
|
let progid_ext_key = self
|
||||||
|
.ext_keys
|
||||||
|
.get(index)
|
||||||
|
.ok_or(ProgramError::BadIndex(index))?;
|
||||||
|
|
||||||
// Try resolving name with string resource first,
|
// Try resolving name with string resource first,
|
||||||
// and fallback to ProgId verbatim.
|
// and fallback to ProgId verbatim.
|
||||||
@@ -387,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()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -402,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())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,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()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -438,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)?;
|
||||||
@@ -455,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...");
|
||||||
@@ -475,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()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -488,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()
|
||||||
);
|
);
|
||||||
@@ -503,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.
|
||||||
@@ -512,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()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -525,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.
|
||||||
@@ -534,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,
|
||||||
@@ -543,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()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -593,7 +636,7 @@ impl Program {
|
|||||||
// 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)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -649,6 +692,7 @@ pub struct ProgramSelfExtStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProgramSelfExtStatus {
|
impl ProgramSelfExtStatus {
|
||||||
|
/// Create a new `ProgramSelfExtStatus`.
|
||||||
fn new(ext: concept::Ext, name: String, icon: concept::IconRc) -> Self {
|
fn new(ext: concept::Ext, name: String, icon: concept::IconRc) -> Self {
|
||||||
Self { ext, name, icon }
|
Self { ext, name, icon }
|
||||||
}
|
}
|
||||||
@@ -689,6 +733,7 @@ pub struct ProgramExtStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ProgramExtStatus {
|
impl ProgramExtStatus {
|
||||||
|
/// Create a new `ProgramExtStatus`.
|
||||||
fn new(name: String, icon: concept::IconRc) -> Self {
|
fn new(name: String, icon: concept::IconRc) -> Self {
|
||||||
Self { name, icon }
|
Self { name, 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;
|
||||||
+198
-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,7 +568,16 @@ 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,
|
||||||
@@ -526,6 +599,9 @@ impl IconRc {
|
|||||||
|
|
||||||
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.
|
||||||
@@ -562,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
|
||||||
}
|
}
|
||||||
@@ -622,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::{
|
||||||
@@ -673,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
|
||||||
}
|
}
|
||||||
@@ -717,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,
|
||||||
@@ -737,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;
|
||||||
|
|
||||||
@@ -798,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.
|
||||||
@@ -824,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 {
|
||||||
@@ -871,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.
|
||||||
@@ -915,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,
|
||||||
}
|
}
|
||||||
@@ -956,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(),
|
||||||
@@ -1025,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::{
|
||||||
|
|||||||
+13
-2
@@ -9,8 +9,19 @@ pub fn check_sandbox() {
|
|||||||
std::env::var("SANDBOXIE").is_ok(),
|
std::env::var("SANDBOXIE").is_ok(),
|
||||||
concat!(
|
concat!(
|
||||||
"Non-sandbox environment detected. ",
|
"Non-sandbox environment detected. ",
|
||||||
"Executing these test in non-sandbox environment is VERY dangerous. ",
|
"Executing these tests in non-sandbox environment is VERY dangerous. ",
|
||||||
"Please set \"SANDBOXIE\" environment variable to explicitly indicate you are running these test in sandbox environment."
|
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."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,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());
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ fn make_valid_schema() -> Schema {
|
|||||||
schema.set_clsid(CLSID);
|
schema.set_clsid(CLSID);
|
||||||
schema.add_str("main_name", "Passoc Application").unwrap();
|
schema.add_str("main_name", "Passoc Application").unwrap();
|
||||||
schema.add_str("ext_name", "Pacfg File").unwrap();
|
schema.add_str("ext_name", "Pacfg File").unwrap();
|
||||||
schema.add_icon("main_icon", r"notepad.exe,0").unwrap();
|
schema.add_icon("main_icon", "notepad.exe,0").unwrap();
|
||||||
schema.add_icon("ext_icon", r"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("main_behavior", "notepad.exe %1").unwrap();
|
||||||
schema.add_behavior("ext_behavior", "notepad.exe %1").unwrap();
|
schema.add_behavior("ext_behavior", "notepad.exe %1").unwrap();
|
||||||
schema.set_name(Some("main_name"));
|
schema.set_name(Some("main_name"));
|
||||||
@@ -29,21 +29,20 @@ fn make_valid_schema() -> Schema {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_schema() {
|
fn test_schema() {
|
||||||
common::check_sandbox();
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
// valid schema -> valid program
|
// valid schema -> valid program
|
||||||
let schema = make_valid_schema();
|
let schema = make_valid_schema();
|
||||||
let rv = schema.into_program();
|
let rv = schema.into_program();
|
||||||
assert!(rv.is_ok());
|
assert!(rv.is_ok());
|
||||||
|
|
||||||
// missing identifier
|
// missing essential parts (schema, path and etc)
|
||||||
let mut schema = Schema::new();
|
let schema = Schema::new();
|
||||||
schema.set_path(APP_PATH);
|
|
||||||
let rv = schema.into_program();
|
let rv = schema.into_program();
|
||||||
assert!(rv.is_err());
|
assert!(rv.is_err());
|
||||||
|
|
||||||
// invalid path
|
// invalid path
|
||||||
let mut schema = Schema::new();
|
let mut schema = make_valid_schema();
|
||||||
schema.set_identifier(IDENTIFIER);
|
|
||||||
schema.set_path(r"C:\");
|
schema.set_path(r"C:\");
|
||||||
let rv = schema.into_program();
|
let rv = schema.into_program();
|
||||||
assert!(rv.is_err());
|
assert!(rv.is_err());
|
||||||
@@ -93,6 +92,7 @@ fn test_schema() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_program() {
|
fn test_program() {
|
||||||
common::check_sandbox();
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
fn tester(scope: Scope, view: View) {
|
fn tester(scope: Scope, view: View) {
|
||||||
// build program
|
// build program
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ static VERB: LazyLock<ShellVerb> = LazyLock::new(|| {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_app_paths_key() {
|
fn test_app_paths_key() {
|
||||||
common::check_sandbox();
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
static APP_PATH: &str = r"C:\Program Files\Passoc\passoc.exe";
|
static APP_PATH: &str = r"C:\Program Files\Passoc\passoc.exe";
|
||||||
static APP_DIR: &str = r"C:\Program Files\Passoc";
|
static APP_DIR: &str = r"C:\Program Files\Passoc";
|
||||||
@@ -75,6 +76,7 @@ fn test_app_paths_key() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_applications_key() {
|
fn test_applications_key() {
|
||||||
common::check_sandbox();
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
static FRIENDLY_APP_NAME: LazyLock<StrResVariant> =
|
static FRIENDLY_APP_NAME: LazyLock<StrResVariant> =
|
||||||
LazyLock::new(|| "Passoc Application".into());
|
LazyLock::new(|| "Passoc Application".into());
|
||||||
@@ -177,6 +179,7 @@ fn test_applications_key() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_ext_key() {
|
fn test_ext_key() {
|
||||||
common::check_sandbox();
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
fn tester(scope: Scope, view: View) {
|
fn tester(scope: Scope, view: View) {
|
||||||
let mut key = ExtKey::new(EXT.clone());
|
let mut key = ExtKey::new(EXT.clone());
|
||||||
@@ -246,6 +249,7 @@ fn test_ext_key() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_prog_id_key() {
|
fn test_prog_id_key() {
|
||||||
common::check_sandbox();
|
common::check_sandbox();
|
||||||
|
common::check_privilege();
|
||||||
|
|
||||||
static LEGACY_NAME: LazyLock<StrResVariant> = LazyLock::new(|| "Passoc Pacfg File".into());
|
static LEGACY_NAME: LazyLock<StrResVariant> = LazyLock::new(|| "Passoc Pacfg File".into());
|
||||||
static FRIENDLY_TYPE_NAME: LazyLock<StrResVariant> =
|
static FRIENDLY_TYPE_NAME: LazyLock<StrResVariant> =
|
||||||
|
|||||||
Reference in New Issue
Block a user