1
0

Compare commits

..

13 Commits

40 changed files with 2976 additions and 2081 deletions

View File

@@ -1,3 +0,0 @@
# Pineapple Picture Association
TODO

241
example/qwfassoc/PROMPT.txt Normal file
View File

@@ -0,0 +1,241 @@
我要求你使用Qt Widget编写一个GUI程序。该GUI程序是wfassoc的一个可视化界面。wfassoc是一个由Rust编写并暴露出C接口可以操作Windows注册表来管理应用程序的注册和卸载以及文件关联的动态链接库。
我要求你在@example/qwfassoc/TASKS.md 中先做好详细的规划,而不是上来就写代码。我会安排其他人来负责执行你做的规划。
# 项目要求
- 使用Qt Widget编写界面而不是QML。
- 使用UI文件而不是C++语句来构建界面。
- 我使用的是Qt 6使用CMake作为构建系统不要使用Qt的qmake。
- 使用toml11作为TOML读取库。
# 界面要求
这是一个基于 Qt Widgets 的标准对话框界面描述。你可以按照以下层级结构来构建代码:
## 主窗口容器 (Main Window)
* 类: `QDialog`。
* 窗口标题: "xxx选项"。xxx在应用程序初始化时通过wfassoc的Program提供的接口运行时获取。
* 窗口图标在应用程序初始化时通过wfassoc的Program提供的接口运行时获取。
* 大小限制固定大小480x600
## 选项卡 (Top Tabs)
* 组件: `QTabWidget`。
* 标签页 (Tabs): 从左到右依次添加以下标签页:
1. 应用程序
2. 文件关联
* 大小:选项卡占据对话框全部内容
## "应用程序"选项卡内容
该选项卡内部使用垂直布局 (`QVBoxLayout`),包含一个主要的分组区域:
### 区域内容
* 容器: `QGroupBox`。
* 区域标题:安装与卸载
* 布局: 垂直布局 (`QVBoxLayout`)。
* 上半部分
* 水平布局(`QHBoxLayout`)。
* 左侧: 一个 `QLabel` 显示图标该图标表示要设定的应用程序的图标。在应用程序初始化时通过wfassoc的Program提供的接口运行时获取。
* 右侧: 一个 `QLabel` 显示文本"在此安装或卸载xxx"表示要设定的应用程序的名称。xxx在应用程序初始化时通过wfassoc的Program提供的接口运行时获取。 (文本需要设置自动换行 `setWordWrap(true)`)。
* 下半部分
* 水平布局(`QHBoxLayout`)。
* 内容为两个 `QPushButton`,文本分别为:
* 安装:为当前对象(系统或当前用户,由应用程序初始化时从命令行参数获取)安装应用。如果应用程序已经安装,则不可点击。
* 卸载:为当前对象(系统或当前用户,由应用程序初始化时从命令行参数获取)卸载应用。如果应用程序没有安装,则不可点击。
## "文件关联"选项卡内容
在这个选项页内部,使用一个垂直布局 (`QVBoxLayout`) 来排列以下控件:
* 说明文本:
* 组件: `QLabel`。
* 文本: "使用 xxx 关联的文件类型:"。xxx在应用程序初始化时通过wfassoc的Program提供的接口运行时获取。
* 功能按钮行:
* 布局: `QHBoxLayout` (水平布局)。
* 组件: 两个 `QPushButton`。
* 文本: 两个按钮的文本都是 "+"。
* 位置: 位于列表上方,用于“全选”操作(第一次点击,将所有没关联的文件扩展名(显示为空白)设置为应用程序提供的打开方式。如果没有空白内容,或第二次点击,将所有文件全部设置为应用程序提供的打开方式)。
* 文件类型列表 (核心组件):
* 组件: `QTableWidget` (表格控件)。
* 列数: 3列。
* 表头 (Header):
* 第1列标题: "类型"
* 第2列标题: "uuu" (uuu在运行时进行获取为当前用户名)
* 第3列标题: "所有用户"
* 行内容示例:
* 第一列一个文件类型图标右边跟着对应的文本。表示当前文件扩展名和当前混合视图hybrid下的图标。图标和文本均从wfassoc函数获取。
* 第二列用户视图user下的名称。文本从wfassoc函数获取。
* 第三列系统视图system下的名称。文本从wfassoc函数获取。
* 滚动条: 右侧有一个垂直滚动条 (`QScrollBar`),表示内容超出可视区域。
* 操作方式:
* 第二列和第三列的元素可点击。
* 如果元素为空白或其它打开方式则点击后设置为当前应用程序指定的打开方式link
* 如果是自身打开方式点击后设置为空白unlink
* 点击后,第一列的图标需要改变,也因此你需要暂存当前应用程序提供打开方式的图标。如果第二第三列均为空,则不显示图标(仍然占位,显示为空白)。
* 点击操作并不会实时操作注册表,程序需要暂存用户的需求,然后在点击确认或应用按钮后再统一执行。
* 底部按钮栏
* 布局: `QHBoxLayout` (水平布局),通常右对齐或使用 `QDialogButtonBox`。
* 组件: 三个 `QPushButton`。
* 按钮文本 (从左到右):
1. "确定" (通常设为默认按钮 `setDefault(true)`)。
2. "取消"。
3. "应用":点击后应用修改,并留在页面 (如果没有修改,则不可用)。
额外注意:
* 如果应用程序没有安装,则本页面下所有内容均不启用。
* 如果启动时命令行指定以为当前用户安装的模式启动,则系统那一栏所有按钮都不可用
# 代码要求
- 有关wfassoc的接口请查阅@wfassoc-cdylib/codegen/wfassoc++.h 我要求你使用这个头文件中提供的内容来进行编写。
- wfassoc++.h文件中没有注释如果你想查看注释请访问@wfassoc-cdylib/codegen/wfassoc.h 文件。wfassoc++.h是wfassoc.h的C++包装。
- wfassoc.h所暴露的接口是由Rust编写的通常查看wfassoc.h可满足所有需求。如果仍有不确定的内容可查看其对应Rust项目的源码位于@wfassoc-cdylib/src 目录下。或更进一步地查看其依赖的源码,位于@wfassoc/src 目录下。
- 该GUI程序需要接受两个必要的命令行参数请使用Qt内置的命令行解析器进行解析
- `-c`或`--manifest`:指定要配置的应用程序的清单文件。
- `-f`或`--for`:指定应用程序要安装到的
- 清单文件的样例是@example/manifest/ppic.toml
- 在应用程序加载时或者执行操作时如果发生错误例如底层wfassoc发生错误丢失命令行选项等则弹出对话框报错然后立即退出程序
- 程序基本流程:
- 接受命令行,检查命令行参数是否合法
- 加载命令行指定的manifest文件并使用sanitizer检查错误。你可以阅读@wfassoc-exec/src/manifest.rs 文件来看看我是如何在Rust中检查它的。
- 按照给定的manifest文件使用wfassoc库构建schema然后再构建program。
- 初始化窗口。
- 调用wfassoc program提供的函数检查应用程序是否安装设置窗口的安装部分的按钮enable。
- 调用wfassoc program提供的函数遍历所有文件扩展名和关联情况设置窗口的文件关联表格。
- 用户可以在"应用程序"选项卡中注册和卸载应用程序点击后弹出窗口表示安装或卸载成功然后检测是否安装并刷新各个控件的enable状态。
- 用户可以在"文件关联"选项卡中设置是否以当前应用程序打开某些扩展名。用户可以点击全选按钮或单元格来进行设置,应用程序暂存修改,等用户点击确认或应用后再应用修改。如果点击的是应用,则刷新当前页面。
# 额外要求
- 不要尝试去编译来检查错误。我会安排其他人来检查程序是否能正常运行,并汇报回来,你再修改。
- 你不需要关心能否找到Qtwfassoc和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部分的任务规划就非常符合这种范式。

View File

@@ -0,0 +1,3 @@
# Q WFAssoc
TODO

1313
example/qwfassoc/TASKS.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
# This module requires the user to set wfassoc_ROOT to the installation
# directory of wfassoc. The directory structure under wfassoc_ROOT must be:
# bin/ - contains wfassoc_cdylib.dll
# include/ - contains wfassoc.h
# include/ - contains wfassoc.h and wfassoc++.h
# lib/ - contains wfassoc_cdylib.dll.lib (import library)
#
# This module defines the following variables:
@@ -36,7 +36,7 @@ set(wfassoc_LIB_DIR ${wfassoc_ROOT}/lib)
set(wfassoc_BIN_DIR ${wfassoc_ROOT}/bin)
# Find header files
if(EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc.h)
if(EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc.h AND EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc++.h)
set(wfassoc_INCLUDE_DIRS ${wfassoc_INCLUDE_DIR})
else()
message(SEND_ERROR "Missing wfassoc header files in ${wfassoc_INCLUDE_DIR}")

View File

@@ -0,0 +1,5 @@
# cmake 模块说明
此目录下的 `Findwfassoc.cmake` 是从项目根目录 `wfassoc-cdylib/codegen/Findwfassoc.cmake` 复制而来。
该文件提供 `wfassoc::wfassoc` imported target。使用前需要设置 `wfassoc_ROOT` 变量指向 wfassoc 安装目录。

405
legacy/.gitignore vendored
View File

@@ -1,405 +0,0 @@
# Custom ignore
# ignore build directory
builds/
Debug_MB/
Debug_UNICODE/
Release_UNICODE/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml

View File

@@ -1,10 +0,0 @@
# wfassoc
**W**indows **F**ile **Assoc**iation Library
**Work In Progress**
## Introduction
* wfassoc: Main library
* wfassoc_example: A full example about how to use this library

View File

@@ -1,51 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31702.278
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wfassoc", "wfassoc\wfassoc.vcxproj", "{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wfassoc_example", "wfassoc_example\wfassoc_example.vcxproj", "{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug_MB|x64 = Debug_MB|x64
Debug_MB|x86 = Debug_MB|x86
Debug_UNICODE|x64 = Debug_UNICODE|x64
Debug_UNICODE|x86 = Debug_UNICODE|x86
Release_UNICODE|x64 = Release_UNICODE|x64
Release_UNICODE|x86 = Release_UNICODE|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x64.ActiveCfg = Debug|x64
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x64.Build.0 = Debug|x64
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x86.ActiveCfg = Debug|Win32
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_MB|x86.Build.0 = Debug|Win32
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x64.ActiveCfg = Debug|x64
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x64.Build.0 = Debug|x64
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x86.ActiveCfg = Debug|Win32
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Debug_UNICODE|x86.Build.0 = Debug|Win32
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x64.ActiveCfg = Release|x64
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x64.Build.0 = Release|x64
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x86.ActiveCfg = Release|Win32
{4AAC8F0C-3E1C-4584-B682-05BBF96A813F}.Release_UNICODE|x86.Build.0 = Release|Win32
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x64.ActiveCfg = Debug_UNICODE|x64
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x64.Build.0 = Debug_UNICODE|x64
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x86.ActiveCfg = Debug_MB|Win32
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_MB|x86.Build.0 = Debug_MB|Win32
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x64.ActiveCfg = Debug_UNICODE|x64
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x64.Build.0 = Debug_UNICODE|x64
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x86.ActiveCfg = Debug_UNICODE|Win32
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Debug_UNICODE|x86.Build.0 = Debug_UNICODE|Win32
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x64.ActiveCfg = Release_UNICODE|x64
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x64.Build.0 = Release_UNICODE|x64
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x86.ActiveCfg = Release_UNICODE|Win32
{AD275AD7-CBD5-41CA-AB24-BB707B3F7534}.Release_UNICODE|x86.Build.0 = Release_UNICODE|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1AD32362-840E-4399-A7D5-26A0B67A614D}
EndGlobalSection
EndGlobal

View File

@@ -1,9 +0,0 @@
LIBRARY wfassoc
EXPORTS
WFInstallApplicationW
WFInstallApplicationA
WFUninstallApplicationW
WFUninstallApplicationA
WFGenerateProgIDW
WFGenerateProgIDA

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{4aac8f0c-3e1c-4584-b682-05bbf96a813f}</ProjectGuid>
<RootNamespace>wfassoc</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>NotSet</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)builds\Debug\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)builds\Release\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<ModuleDefinitionFile>wfassoc.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="wfassoc_utils.h" />
<ClInclude Include="wfassoc_core.h" />
<ClInclude Include="wfassoc_private.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="wfassoc_private.c" />
<ClCompile Include="wfassoc_utils.c" />
<ClCompile Include="wfassoc_core.c" />
</ItemGroup>
<ItemGroup>
<None Include="wfassoc.def" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="wfassoc_core.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="wfassoc_utils.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="wfassoc_private.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="wfassoc_core.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="wfassoc_utils.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="wfassoc_private.c">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="wfassoc.def">
<Filter>源文件</Filter>
</None>
</ItemGroup>
</Project>

View File

@@ -1,407 +0,0 @@
#include "wfassoc_core.h"
#include <strsafe.h>
#include <ShlObj.h>
// private function and variable declearions.
// the function with _AL tail mean that the value returned by function is allocated from heap and should be free manually.
// otherwise, the tail of _NAL mean this function will not allocate any new memeory.
/// <summary>
/// Convert multi byte string to wide char string
/// Notice: this function will allocate memory for returns and it should be released safely.
/// </summary>
/// <param name="source">The string will be converted</param>
/// <param name="error">The error happend during converting</param>
/// <returns>The string has been converted. If return NULL, it mean that the converting failed.</returns>
//WFERROR ConvMultiByteToWideChar(CHAR* source);
WFERROR WFSplitAppPath(WFString* app_path, WFString* app_name, WFString* base_path);
//WFERROR Strcat(WCHAR* str1, WCHAR* str2);
//WFERROR WFGetBasePathFromAppPath(WFString* app_path, WFString* base_path);
WFERROR WFSplitSupportedTypesString(wchar_t* typesString, WFLinkedList* list);
void WFPrintflnInDebug();
// some effective reg function
LSTATUS WFRegOpenKeyWithCreation(HKEY hkey, LPCWSTR lpSubKey, PHKEY phkResult);
LSTATUS WFRegSetStringValue(HKEY hkey, LPCWSTR lpValueName, const WCHAR* data);
#define LEGACY_PROGID_FORMAT L"%s.%s.%d"
#define WFALLOC(type,count) (type*)malloc(sizeof(type)*count);
#define SAFE_EXEC_WIN32(wfError, recvError, skipLabel, function) recvError=function; if(recvError!=ERROR_SUCCESS) {wfError = WFERROR_WIN32;goto skipLabel;}
#define SAFE_EXEC_WF_LABEL(wfError, skipLabel, function) if((wfError=function)!=WFERROR_OK) {goto skipLabel;}
#define SAFE_EXEC_WF_RETURN(wfError, function) if((wfError=function)!=WFERROR_OK) {return wfError;}
#define SAFE_EXEC_STRALLOC(wfError, skipLabel, vStr, function) vStr=function; if(vStr==NULL) {wfError = WFERROR_ALLOC;goto skipLabel;}
#define SAFE_FREE_PTR(obj) if(obj!=NULL){free(obj);obj=NULL;}
#define SAFE_FREE_HKEY(hkey) if(hkey!=NULL&&hkey!=INVALID_HANDLE_VALUE){RegCloseKey(hkey);hkey=NULL;}
// public function implements.
WFERROR WFInstallApplication(WFAPP_PROFILE* profile) {
if (profile->WFVersion != WFVERSION) return WFERROR_INVALID_VERSION;
WFERROR wf_error = WFERROR_OK;
LSTATUS win32_error = ERROR_SUCCESS;
//WCHAR* itemSupportedTypes = NULL;
// ==================================
// generate necessary data
// init string
WFString* strProgID = NULL,
* strAppPath = NULL,
* strAppBasePath = NULL,
* strAppFileName = NULL;
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Alloc(&strProgID)
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Alloc_Wchar(&strAppPath, profile->AppPath)
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Alloc(&strAppBasePath)
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Alloc(&strAppFileName)
);
// write string
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Printf(strProgID, LEGACY_PROGID_FORMAT, profile->ProgID_Vendor, profile->ProgID_Component, profile->ProgID_Version)
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFSplitAppPath(strAppPath, strAppFileName, strAppBasePath)
);
WFString* regpathAppPaths = NULL,
* regpathApplicationsRealName = NULL,
* regpathApplicationsProgId = NULL;
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Alloc_Wchar(&regpathAppPaths, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\")
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Concat_String(regpathAppPaths, strAppFileName)
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Alloc_Wchar(&regpathApplicationsRealName, L"SOFTWARE\\Classes\\Applications\\")
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Concat_String(regpathApplicationsRealName, strAppFileName)
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Alloc_Wchar(&regpathApplicationsProgId, L"SOFTWARE\\Classes\\Applications\\")
);
SAFE_EXEC_WF_LABEL(wf_error, final_process,
WFString_Concat_String(regpathApplicationsProgId, strProgID)
);
//WCHAR* strAppPath = NULL,
// * strPath = NULL,
// * strApplications = NULL,
// * strProgID = NULL,
// * strOpenWithList = NULL;
//SAFE_EXEC_STRALLOC(error, final_process,
// strPath, GetPathFromAppPath_AL(profile->AppPath)
//);
//SAFE_EXEC_STRALLOC(error, final_process,
// strAppPath, Strcat_AL(
// L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\",
// GetAppNameFromAppPath_NAL(profile->AppPath)
//)
//);
//SAFE_EXEC_STRALLOC(error, final_process,
// strProgID, Strcat_AL(
// L"SOFTWARE\\Classes\\Applications\\",
// profile->ProgID
//)
//);
//SAFE_EXEC_STRALLOC(error, final_process,
// strApplications, Strcat_AL(
// L"SOFTWARE\\Classes\\Applications\\",
// GetAppNameFromAppPath_NAL(profile->AppPath)
//)
//);
// generate necessary HKEY
HKEY nodeAppPath = NULL,
nodeApplications = NULL,
nodeApplications_Verb = NULL,
nodeApplications_SupportedTypes = NULL,
nodeProgID = NULL,
nodeProgID_Verb = NULL,
nodeClasses = NULL,
nodeExt = NULL,
nodeExt_OpenWithProgIds = NULL,
nodeExt_OpenWithList = NULL;
// register in `App Paths`
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, strAppPath, &nodeAppPath)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, strApplications, &nodeApplications)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(nodeApplications, L"shell\\open\\command", &nodeApplications_Verb)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(nodeApplications, L"SupportedTypes", &nodeApplications_SupportedTypes)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, strProgID, &nodeProgID)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(nodeProgID, L"shell\\open\\command", &nodeProgID_Verb)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(profile->RegisterForAllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"SOFTWARE\\Classes", &nodeClasses)
);
// operate HKEY
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegSetStringValue(nodeAppPath, NULL, profile->AppPath) // visit Default key
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegSetStringValue(nodeAppPath, L"Path", strPath)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegSetStringValue(nodeApplications_Verb, NULL, profile->AppCommand) // visit Default key
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegSetStringValue(nodeProgID_Verb, NULL, profile->AppCommand) // visit Default key
);
while ((itemSupportedTypes = IterateSupportedTypesString_NAL(profile->SupportedTypes, itemSupportedTypes)) != NULL) {
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegSetStringValue(nodeApplications_SupportedTypes, itemSupportedTypes, NULL) // register supported type with blank item
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(nodeClasses, itemSupportedTypes, &nodeExt)
);
if (profile->SetAsDefault) {
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegSetStringValue(nodeExt, NULL, profile->ProgID) // visit Default key
);
}
if (profile->ShowInOpenWithMenu) {
if (profile->UseOpenWithList) {
// use Windows XP node
SAFE_EXEC_STRALLOC(error, final_process,
strOpenWithList, Strcat_AL(
L"OpenWithList\\",
GetAppNameFromAppPath_NAL(profile->AppPath)
)
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(nodeExt, itemSupportedTypes, &nodeExt_OpenWithList);
);
SAFE_FREE_PTR(strOpenWithList);
SAFE_FREE_HKEY(nodeExt_OpenWithList);
} else {
// use Windows Vista node
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegOpenKeyWithCreation(nodeExt, itemSupportedTypes, &nodeExt_OpenWithProgIds);
);
SAFE_EXEC_WIN32(error, win32_error, final_process,
WFRegSetStringValue(nodeExt_OpenWithProgIds, profile->ProgID, NULL)
);
SAFE_FREE_HKEY(nodeExt_OpenWithProgIds);
}
}
SAFE_FREE_HKEY(nodeExt);
}
final_process:
// free HKEY and strings
SAFE_FREE_HKEY(nodeAppPath);
SAFE_FREE_HKEY(nodeApplications);
SAFE_FREE_HKEY(nodeApplications_Verb);
SAFE_FREE_HKEY(nodeApplications_SupportedTypes);
SAFE_FREE_HKEY(nodeProgID);
SAFE_FREE_HKEY(nodeProgID_Verb);
SAFE_FREE_HKEY(nodeClasses);
SAFE_FREE_HKEY(nodeExt);
SAFE_FREE_HKEY(nodeExt_OpenWithProgIds);
SAFE_FREE_HKEY(nodeExt_OpenWithList);
SAFE_FREE_PTR(strAppPath);
SAFE_FREE_PTR(strPath);
SAFE_FREE_PTR(strApplications);
SAFE_FREE_PTR(strProgID);
// order uninstall to wipe out all written data
// if function failed
if (error != WFERROR_OK)
WFUninstallApplicationW(profile);
// active changes
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
// return error
return error;
}
//WFERROR WFInstallApplicationA(WFAPP_PROFILEA* profile) {
// return WFERROR_OK;
//}
//
//WFERROR WFUninstallApplicationW(WFAPP_PROFILEW* profile) {
// if (profile->WFVersion != WFVERSION) return WFERROR_INVALID_VERSION;
//
// return WFERROR_OK;
//}
//
//WFERROR WFUninstallApplicationA(WFAPP_PROFILEA* profile) {
// return WFERROR_OK;
//}
//
//WFERROR WFGenerateProgIDW(WCHAR* vendor, WCHAR* component, INT version, WCHAR* result, INT* result_length) {
// if (result_length == NULL) return WFERROR_NULLPTR;
// if (result == NULL) {
// *result_length = _snwprintf(NULL, 0, LEGACY_PROGID_FORMATW, vendor, component, version) + 1;
// } else {
// int write_result = _snwprintf(result, *result_length, LEGACY_PROGID_FORMATW, vendor, component, version);
// if (write_result < 0 || write_result >= *result_length) return WFERROR_INSUFFICIENT_BUFFER;
// }
// return WFERROR_OK;
//}
//
//WFERROR WFGenerateProgIDA(CHAR* vendor, CHAR* component, INT version, CHAR* result, INT* result_length) {
// if (result_length == NULL) return WFERROR_NULLPTR;
// if (result == NULL) {
// *result_length = _snprintf(NULL, 0, LEGACY_PROGID_FORMATA, vendor, component, version) + 1;
// } else {
// int write_result = _snprintf(result, *result_length, LEGACY_PROGID_FORMATA, vendor, component, version);
// if (write_result < 0 || write_result >= *result_length) return WFERROR_INSUFFICIENT_BUFFER;
// }
// return WFERROR_OK;
//}
// private function and variable implements.
//WCHAR* ConvMultiByteToWideChar_AL(CHAR* source) {
// WCHAR* dest = NULL;
// size_t sourceLength = strlen(source);
//
// int destLength = MultiByteToWideChar(CP_ACP, 0, source, sourceLength, NULL, 0);
// if (destLength <= 0) return NULL;
// destLength += 10;
//
// dest = WFALLOC(WCHAR, destLength);
// if (dest == NULL) return NULL;
//
// memset(dest, 0, sizeof(WCHAR) * destLength);
// int error = MultiByteToWideChar(CP_ACP, 0, source, sourceLength, dest, destLength);
// if (error <= 0) {
// free(dest);
// return NULL;
// }
//
// return dest;
//}
WFERROR WFSplitAppPath(WFString* app_path, WFString* app_name, WFString* base_path) {
WFERROR ec;
wchar_t* lastSlash, *src, *ptr;
SAFE_EXEC_WF_RETURN(ec, WFString_GetData(app_path, &ptr));
src = lastSlash = ptr;
while (*ptr != L'\0') {
if (*ptr == L'\\' || *ptr == L'/') {
lastSlash = ptr;
}
ptr++;
}
SAFE_EXEC_WF_RETURN(ec, WFString_SubString(app_path, base_path, 0, lastSlash - src));
SAFE_EXEC_WF_RETURN(ec, WFString_SetData(app_name, ++lastSlash));
return WFERROR_OK;
}
//WCHAR* Strcat_AL(WCHAR* str1, WCHAR* str2) {
// size_t length = wcslen(str1) + wcslen(str2) + 10;
//
// WCHAR* dest = NULL;
// dest = WFALLOC(WCHAR, length);
// if (dest == NULL) return NULL;
//
// wcscpy(dest, str1);
// wcscat(dest, str2);
// return dest;
//}
WFERROR WFSplitSupportedTypesString(wchar_t* typesString, WFLinkedList* list) {
WFERROR ec;
wchar_t* ptr;
WFString* strl;
uint32_t len_str;
ptr = typesString;
while (*ptr != L'\0') {
// add into list
SAFE_EXEC_WF_RETURN(ec, WFString_Alloc(&strl, ptr));
SAFE_EXEC_WF_RETURN(ec, WFLinkedList_Add(list, strl));
// shift to next string
SAFE_EXEC_WF_RETURN(ec, WFString_GetLength(strl, &len_str));
ptr += len_str + 1;
}
return WFERROR_OK;
}
//WCHAR* IterateSupportedTypesString_NAL(WCHAR* typesString, WCHAR* prev) {
// if (typesString == NULL || *typesString == L'\0') return NULL;
// if (prev == NULL) return typesString;
//
// // skip prev string
// while (*prev != L'\0') {
// prev++;
// }
//
// // if the next string is start with zero, it mean that the full string is over and return NULL to terminate analyse.
// // otherwise return next string
// prev++;
// if (*prev == L'\0') return NULL;
// else return prev;
//}
void WFPrintflnInDebug() {
}
LSTATUS WFRegOpenKeyWithCreation(HKEY hkey, LPCWSTR lpSubKey, PHKEY phkResult) {
return RegCreateKeyExW(
hkey,
lpSubKey,
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,
NULL,
phkResult,
NULL
);
}
LSTATUS WFRegSetStringValue(HKEY hkey, LPCWSTR lpValueName, const WCHAR* data) {
return RegSetValueExW(
hkey,
lpValueName,
0,
REG_SZ,
data,
data == NULL ? 0 : ((wcslen(data) + 1) * sizeof(WCHAR))
);
}

View File

@@ -1,60 +0,0 @@
#if !defined(_YYCDLL_WFASSOC_H__IMPORTED_)
#define _YYCDLL_WFASSOC_H__IMPORTED_
#include <Windows.h>
#include <sal.h>
#include "wfassoc_utils.h"
// quick marco for developer and should not be used in wfassoc self
//#if defined(_UNICODE)
//#define WFAPP_PROFILE WFAPP_PROFILEW
//#define WFInstallApplication WFInstallApplicationW
//#define WFUninstallApplication WFUninstallApplicationW
//#define WFGenerateProgID WFGenerateProgIDW
//#elif defined(_MBCS)
//#define WFAPP_PROFILE WFAPP_PROFILEA
//#define WFInstallApplication WFInstallApplicationA
//#define WFUninstallApplication WFUninstallApplicationA
//#define WFGenerateProgID WFGenerateProgIDA
//#endif
/// <summary>
/// Install Application via Wide Character
/// </summary>
/// <param name="profile"></param>
/// <returns></returns>
WFERROR WFInstallApplication(WFAPP_PROFILE* profile);
WFERROR WFUninstallApplication(WFAPP_PROFILE* profile);
WFERROR WFRegisterAppPath(WFAPP_INTERNAL_PROFILE* internal_profile);
WFERROR WFRegisterApplication(WFAPP_INTERNAL_PROFILE* internal_profile);
WFERROR WFRegisterExtensions(WFAPP_INTERNAL_PROFILE* internal_profile);
//WFERROR WFProfile_Alloc(WFAPP_PROFILE** profile);
//WFERROR WFProfile_Free(WFAPP_PROFILE* profile);
//WFERROR WFProfile_SetProgIDA(WFAPP_PROFILE** profile, char* vendor, char* component, uint32_t version, BOOL is_utf8);
//WFERROR WFProfile_SetProgIDW(WFAPP_PROFILE** profile, wchar_t* vendor, wchar_t* component, uint32_t version);
/// <summary>
/// Generate Legacy ProgID
/// </summary>
/// <param name="vendor">Vendor. Such as `Word`, `Excel`, `PowerPoint`.</param>
/// <param name="component">Component. Such as `Document`, `Sheet`, `Diagram`.</param>
/// <param name="version">Version. Such as `0`, `1`, `114514`.</param>
/// <param name="result">Pointer to output ProgID. If this variable is NULL, function will calculate proper length of receiving buffer and return it via `result_length`.</param>
/// <param name="result_length">Pointer to a int variable containing buffer's length. If `result` is not NULL, it should be the length of `result`.</param>
/// <returns></returns>
//WFERROR WFGenerateProgIDW(WCHAR* vendor, WCHAR* component, INT version, WCHAR* result, INT* result_length);
/// <summary>
/// Generate Legacy ProgID
/// </summary>
/// <param name="vendor">Vendor. Such as `Word`, `Excel`, `PowerPoint`.</param>
/// <param name="component">Component. Such as `Document`, `Sheet`, `Diagram`.</param>
/// <param name="version">Version. Such as `0`, `1`, `114514`.</param>
/// <param name="result">Pointer to output ProgID. If this variable is NULL, function will calculate proper length of receiving buffer and return it via `result_length`.</param>
/// <param name="result_length">Pointer to a int variable containing buffer's length. If `result` is not NULL, it should be the length of `result`.</param>
/// <returns></returns>
//WFERROR WFGenerateProgIDA(CHAR* vendor, CHAR* component, INT version, CHAR* result, INT* result_length);
#endif

View File

@@ -1 +0,0 @@
#include "wfassoc_private.h"

View File

@@ -1,9 +0,0 @@
#if !defined(_YYCDLL_WFASSOC_PRIVATE_H__IMPORTED_)
#define _YYCDLL_WFASSOC_PRIVATE_H__IMPORTED_
typedef struct _WFAPP_RAWDATA {
uint32_t mVersion;
}WFAPP_RAWDATA;
#endif

View File

@@ -1,256 +0,0 @@
#include "wfassoc_utils.h"
#pragma region WFString
WFERROR WFString_Alloc_Wchar(WFString** strl, const wchar_t* raw_data) {
if (raw_data == NULL) return WFERROR_NULLPTR;
WFERROR ec;
if ((ec = WFString_Alloc(strl)) != WFERROR_OK) return ec;
if ((ec = WFString_SetData(*strl, raw_data)) != WFERROR_OK) return ec;
return WFERROR_OK;
}
WFERROR WFString_Alloc_Char(WFString** strl, const char* raw_data, BOOL is_utf8) {
if (raw_data == NULL) return WFERROR_NULLPTR;
// init string
WFERROR ec;
if ((ec = WFString_Alloc(strl)) != WFERROR_OK) return ec;
// compute expected string length
uint32_t sourceLength = strlen(raw_data);
int destLength = MultiByteToWideChar(is_utf8 ? CP_UTF8 : CP_ACP, 0, raw_data, sourceLength, NULL, 0);
if (destLength <= 0) return WFERROR_CRT;
--destLength; // remove terminal char
// resize string
if ((ec = WFString_Resize(*strl, destLength)) != WFERROR_OK) return ec;
// clear buffer and write data
wchar_t* buffer;
if ((ec = WFString_GetData(*strl, &buffer)) != WFERROR_OK) return ec;
memset(buffer, 0, sizeof(WCHAR) * destLength);
int crt_error = MultiByteToWideChar(is_utf8 ? CP_UTF8 : CP_ACP, 0, raw_data, sourceLength, buffer, destLength);
if (crt_error <= 0) return WFERROR_CRT;
return WFERROR_OK;
}
WFERROR WFString_Alloc_Capacity(WFString** strl, uint32_t size) {
*strl = WFNEW(WFString);
if (*strl == NULL) return WFERROR_ALLOC;
// init struct data
(*strl)->mCapacity = size;
(*strl)->mRealCapacity = size + 1;
(*strl)->mLength = 0;
(*strl)->mRealLength = 1;
(*strl)->mRawData = malloc((*strl)->mRealCapacity * sizeof(wchar_t));
if ((*strl)->mRawData == NULL) return WFERROR_ALLOC;
(*strl)->mRawData[(*strl)->mRealLength - 1] = 0;
return WFERROR_OK;
}
WFERROR WFString_Alloc(WFString** strl) {
return WFString_Alloc_Capacity(strl, 256);
}
WFERROR WFString_Free(WFString* strl) {
if (strl == NULL) return WFERROR_OK;
if (strl->mRawData == NULL) return WFERROR_NULLPTR;
free(strl->mRawData);
free(strl);
return WFERROR_OK;
}
WFERROR WFString_Resize(WFString* strl, uint32_t new_size) {
// if remain data is not enough, we need alloc new one
if (new_size >= strl->mCapacity) {
strl->mCapacity = new_size * 2;
strl->mRealCapacity = strl->mCapacity + 1;
// alloc buffer
if (strl->mRawData != NULL)
strl->mRawData = realloc(strl->mRawData, strl->mRealCapacity * sizeof(wchar_t));
else
strl->mRawData = malloc(strl->mRealCapacity * sizeof(wchar_t));
if (strl->mRawData == NULL) return WFERROR_ALLOC;
}
// set length as new length
strl->mLength = new_size;
strl->mRealLength = strl->mLength + 1;
// set the last one is zero
strl->mRawData[strl->mRealLength - 1] = 0;
return WFERROR_OK;
}
WFERROR WFString_GetData(WFString* strl, wchar_t** pdata) {
if (strl == NULL) return WFERROR_NULLPTR;
*pdata = strl->mRawData;
return WFERROR_OK;
}
WFERROR WFString_SetData(WFString* strl, const wchar_t* data) {
if (strl == NULL || data == NULL) return WFERROR_NULLPTR;
// get length and resize buffer
WFERROR ec;
uint32_t size = wcslen(data);
ec = WFString_Resize(strl, size);
if (ec != WFERROR_OK) return ec;
// copy data
if (size != 0)
memcpy(strl->mRawData, data, sizeof(wchar_t) * size);
return WFERROR_OK;
}
WFERROR WFString_GetLength(WFString* strl, uint32_t* len) {
if (strl == NULL) return WFERROR_NULLPTR;
*len = strl->mLength;
return WFERROR_OK;
}
WFERROR WFString_Printf(WFString* strl, const wchar_t* format, ...) {
if (strl == NULL) return WFERROR_NULLPTR;
// get expected size
va_list argptr;
va_start(argptr, format);
uint32_t count = _vsnwprintf(NULL, 0, format, argptr);
//count++;
va_end(argptr);
// resize string and get buffer ptr
WFERROR ec;
wchar_t* buffer;
if ((ec = WFString_Resize(strl, count)) != WFERROR_OK) return ec;
if ((ec = WFString_GetData(strl, &buffer)) != WFERROR_OK) return ec;
// write data to buffer
buffer[count - 1] = L'\0';
va_start(argptr, format);
int write_result = _vsnwprintf(buffer, count, format, argptr);
va_end(argptr);
if (write_result < 0 || write_result >= count) return WFERROR_CRT;
return WFERROR_OK;
}
WFERROR WFString_Concat_Wchar(WFString* strl, const wchar_t* extra) {
if (strl == NULL) return WFERROR_NULLPTR;
if (extra == NULL) return WFERROR_OK;
uint32_t count = wcslen(extra);
uint32_t oldlen = strl->mLength;
// if extra strl count is 0, do not copy any data.
if (count == 0) return WFERROR_OK;
WFERROR ec;
if ((ec = WFString_Resize(strl, oldlen + count)) != WFERROR_OK) return ec;
memcpy(strl->mRawData + oldlen, extra, sizeof(wchar_t) * count);
return WFERROR_OK;
}
WFERROR WFString_Concat_String(WFString* strl, WFString* extra) {
if (strl == NULL) return WFERROR_NULLPTR;
if (extra == NULL) return WFERROR_OK;
return WFString_Concat_Wchar(strl, extra->mRawData);
}
WFERROR WFString_SubString(WFString* strl, WFString* substring, uint32_t start_index, uint32_t length) {
if (strl == NULL) return WFERROR_NULLPTR;
if (start_index >= strl->mLength || length > strl->mLength - start_index) return WFERROR_INVALID_ARGUMENTS;
WFERROR ec;
if ((ec = WFString_Resize(substring, length)) != WFERROR_OK) return ec;
if (length != 0) {
memcpy(substring->mRawData, strl->mRawData + start_index, sizeof(wchar_t) * length);
}
return WFERROR_OK;
}
#pragma endregion
#pragma region WFLinkedList
WFERROR WFLinkedList_Alloc(WFLinkedList** list) {
*list = WFNEW(WFLinkedList);
if (*list == NULL) return WFERROR_ALLOC;
return WFERROR_OK;
}
WFERROR WFLinkedList_Free(WFLinkedList* list) {
return WFLinkedList_Free_Full(list, NULL);
}
WFERROR WFLinkedList_Free_Full(WFLinkedList* list, WFLinkedListNode_FreeDataFunc free_func) {
if (list == NULL) return WFERROR_OK;
// iterate full list and remove data and node
WFLinkedListNode* node = list->mHead, * free_node = NULL;
while (node != NULL) {
// free raw data
if (free_func != NULL) (*free_func)(node->mRawData);
// move to next node and free current node
free_node = node;
node = node->mNext;
free(free_node);
}
// free list self
free(list);
return WFERROR_OK;
}
WFERROR WFLinkedList_Add(WFLinkedList* list, void* data) {
if (list == NULL) return WFERROR_NULLPTR;
WFLinkedListNode* new_item = WFNEW(WFLinkedListNode);
if (new_item == NULL) return WFERROR_ALLOC;
new_item->mNext = NULL;
new_item->mRawData = data;
if (list->mLength == 0) {
list->mHead = list->mTail = new_item;
} else {
list->mTail->mNext = new_item;
list->mTail = new_item;
}
++list->mLength;
return WFERROR_OK;
}
WFERROR WFLinkedList_NodeIterator(WFLinkedList* list, WFLinkedListNode** node_ptr) {
if (list == NULL) return WFERROR_NULLPTR;
if (*node_ptr == NULL) {
// if node_ptr is NULL, it mean that wo should iterate this list from head
*node_ptr = list->mHead;
} else {
// otherwise, move to next node
*node_ptr = (*node_ptr)->mNext;
}
// if the header is null, return end of tail error to notice caller stop iterate
if (*node_ptr == NULL) return WFERROR_END_OF_TAIL;
else return WFERROR_OK;
}
WFERROR WFLinkedListNode_GetData(WFLinkedListNode* node, void** pdata) {
if (node == NULL) return WFERROR_NULLPTR;
*pdata = node->mRawData;
return WFERROR_OK;
}
#pragma endregion

View File

@@ -1,136 +0,0 @@
#if !defined(_YYCDLL_WFASSOC_UTILS_H__IMPORTED_)
#define _YYCDLL_WFASSOC_UTILS_H__IMPORTED_
#include <Windows.h>
#include <inttypes.h>
// useful macro
#define WFNEW(type) ((type*)malloc(sizeof(type)))
#define WFNEW_ARRAY(type) ((type*)malloc(sizeof(type)*count);)
#define WFVERSION 0
#define WFSUCCESS(expr) (!expr)
#define WFFAILED(expr) expr
/// <summary>
/// wfassoc Error Enum
/// </summary>
typedef enum _WFERROR {
/// <summary>
/// All operation done successfully
/// </summary>
WFERROR_OK = 0,
/// <summary>
/// The filed `WFVersion` in Profile Struct is not matched. It usually mean that currently used DLL is not matched with the DLL when compiling this application.
/// </summary>
WFERROR_INVALID_VERSION = 1,
/// <summary>
/// The buffer in some operations is insufficient, please try expanding buffer.
/// </summary>
WFERROR_INSUFFICIENT_BUFFER = 2,
/// <summary>
/// Some essential pointer variable is NULL.
/// </summary>
WFERROR_NULLPTR = 3,
WFERROR_WIN32 = 5,
WFERROR_ALLOC = 6,
WFERROR_END_OF_TAIL = 7,
WFERROR_CRT = 8,
WFERROR_INVALID_ARGUMENTS = 9
}WFERROR;
/// <summary>
/// wfassoc Profile Struct
/// </summary>
typedef struct _WFAPP_PROFILE {
/// <summary>
/// wfassoc version. Fill it with `WFVERSION` in your application. This field is used for version checking.
/// </summary>
uint32_t WFVersion;
/// <summary>
/// The path to locate your application.
/// </summary>
wchar_t* AppPath;
/// <summary>
/// The command will be executed when opening files.
/// </summary>
wchar_t* AppCommand;
/// <summary>
/// Your application's ProgID.
/// For more detail about how to create your ProgID, please browse: https://docs.microsoft.com/en-us/windows/win32/shell/fa-progids
/// We also provide a generator for creating legacy ProgID. Use `WFGenerateProgID` to create a legacy ProgID.
/// </summary>
wchar_t* ProgID_Vendor;
wchar_t* ProgID_Component;
uint32_t ProgID_Version;
/// <summary>
/// Your application supported file extensions.
/// The structure of this field is connecting all supported extensions with dot(.) end to end. For example:
/// `.jpg\0.png\0.gif\0`
/// </summary>
wchar_t* SupportedTypes;
BOOL RegisterForAllUsers;
BOOL SetAsDefault;
BOOL ShowInOpenWithMenu;
BOOL UseOpenWithList;
}WFAPP_PROFILE;
typedef struct _WFAPP_INTERNAL_PROFILE {
WFString* AppPath;
WFString* AppBasePath;
WFString* AppFileName;
WFString* ProgID;
WFString* AppCommand;
WFLinkedList* SupportedTypes;
BOOL RegisterForAllUsers;
BOOL SetAsDefault;
BOOL ShowInOpenWithMenu;
BOOL UseOpenWithList;
}WFAPP_INTERNAL_PROFILE;
typedef struct _WFString {
wchar_t* mRawData;
uint32_t mLength;
uint32_t mCapacity;
uint32_t mRealLength;
uint32_t mRealCapacity;
}WFString;
WFERROR WFString_Alloc_Wchar(WFString** strl, const wchar_t* raw_data);
WFERROR WFString_Alloc_Char(WFString** strl, const char* raw_data, BOOL is_utf8);
WFERROR WFString_Alloc_Capacity(WFString** strl, uint32_t size);
WFERROR WFString_Alloc(WFString** strl);
WFERROR WFString_Free(WFString* strl);
WFERROR WFString_Resize(WFString* strl, uint32_t new_size);
WFERROR WFString_GetData(WFString* strl, wchar_t** pdata);
WFERROR WFString_SetData(WFString* strl, const wchar_t* data);
WFERROR WFString_GetLength(WFString* strl, uint32_t* len);
WFERROR WFString_Printf(WFString* strl, const wchar_t* format, ...);
WFERROR WFString_Concat_Wchar(WFString* strl, const wchar_t* extra);
WFERROR WFString_Concat_String(WFString* strl, WFString* extra);
WFERROR WFString_SubString(WFString* strl, WFString* substring, uint32_t start_index, uint32_t length);
typedef WFERROR(*WFLinkedListNode_FreeDataFunc)(void* data);
typedef struct _WFLinkedListNode {
void* mRawData;
WFLinkedListNode* mNext;
}WFLinkedListNode;
typedef struct _WFLinkedList {
WFLinkedListNode* mHead;
WFLinkedListNode* mTail;
uint32_t mLength;
}WFLinkedList;
WFERROR WFLinkedList_Alloc(WFLinkedList** list);
WFERROR WFLinkedList_Free(WFLinkedList* list);
WFERROR WFLinkedList_Free_Full(WFLinkedList* list, WFLinkedListNode_FreeDataFunc free_func);
WFERROR WFLinkedList_Add(WFLinkedList* list, void* data);
WFERROR WFLinkedList_NodeIterator(WFLinkedList* list, WFLinkedListNode** node_ptr);
WFERROR WFLinkedListNode_GetData(WFLinkedListNode* node, void** pdata);
#endif

View File

@@ -1,81 +0,0 @@
#include "../wfassoc/wfassoc.h"
#include <stdio.h>
#include <tchar.h>
#define COMMAND_MAX_LENGTH 128
WFAPP_PROFILE* create_profile();
TCHAR* create_progId();
void free_profile(WFAPP_PROFILE* profile);
int main(int argc, char* args[]) {
// alloc application profile
WFAPP_PROFILE* profile = create_profile();
if (profile == NULL) {
puts("Error: Fail to allocating profile structure.\n");
return 1;
}
// alloc input string and check it
char* input_str = malloc(sizeof(char) * (COMMAND_MAX_LENGTH + 1));
if (input_str == NULL) return;
// accept input
while (TRUE) {
gets_s(input_str, COMMAND_MAX_LENGTH);
if (!strcmp(input_str, "install")) {
_tprintf(TEXT("%s\n"), profile->ProgID);
} else if (!strcmp(input_str, "uninstall")) {
_tprintf(TEXT("%s\n"), profile->ProgID);
} else if (!strcmp(input_str, "quit")) {
break;
}
}
//free input string
free(input_str);
return 0;
}
WFAPP_PROFILE* create_profile() {
WFAPP_PROFILE* profile = (WFAPP_PROFILE*)malloc(sizeof(WFAPP_PROFILE));
if (profile == NULL) return NULL;
memset(profile, 0, sizeof(WFAPP_PROFILE));
profile->WFVersion = WFVERSION;
profile->AppPath = TEXT("E:\\pineapple-picture\\ppic.exe");
profile->AppCommand = TEXT("E:\\pineapple-picture\\ppic.exe \"%1\"");
profile->SupportedTypes = TEXT(".png\0.jpg\0");
profile->ProgID = create_progId();
if (profile->ProgID == NULL) {
free_profile(profile);
return NULL;
}
return profile;
}
TCHAR* create_progId() {
TCHAR* progid = NULL;
int progIdSize;
if (WFFAILED(WFGenerateProgID(TEXT("PineapplePicture"), TEXT("Image"), 0, NULL, &progIdSize))) {
return NULL;
}
progid = (TCHAR*)malloc(sizeof(TCHAR) * progIdSize);
if (progid == NULL) return NULL;
memset(progid, 0, sizeof(TCHAR) * progIdSize);
if (WFFAILED(WFGenerateProgID(TEXT("PineapplePicture"), TEXT("Image"), 0, progid, &progIdSize))) {
free(progid);
return NULL;
}
return progid;
}
void free_profile(WFAPP_PROFILE* profile) {
if (profile->ProgID != NULL) free(profile->ProgID);
free(profile);
}

View File

@@ -1,214 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug_MB|Win32">
<Configuration>Debug_MB</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug_MB|x64">
<Configuration>Debug_MB</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug_UNICODE|Win32">
<Configuration>Debug_UNICODE</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release_UNICODE|Win32">
<Configuration>Release_UNICODE</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug_UNICODE|x64">
<Configuration>Debug_UNICODE</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release_UNICODE|x64">
<Configuration>Release_UNICODE</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{ad275ad7-cbd5-41ca-ab24-bb707b3f7534}</ProjectGuid>
<RootNamespace>wfassocexample</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)builds\Debug\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)builds\Debug\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)builds\Release\</OutDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_UNICODE|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_MB|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_UNICODE|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="..\wfassoc\wfassoc.vcxproj">
<Project>{4aac8f0c-3e1c-4584-b682-05bbf96a813f}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.c" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.c">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,101 @@
# Findwfassoc.cmake
# ----------------
# Find wfassoc library and headers.
#
# This module requires the user to set wfassoc_ROOT to the installation
# directory of wfassoc. The directory structure under wfassoc_ROOT must be:
# bin/ - contains wfassoc_cdylib.dll
# include/ - contains wfassoc.h and wfassoc++.h
# lib/ - contains wfassoc_cdylib.dll.lib (import library)
#
# This module defines the following variables:
# wfassoc_FOUND - True if wfassoc was found
# wfassoc_INCLUDE_DIRS - Path to wfassoc include directory
# wfassoc_LIBRARIES - Path to wfassoc import library
# wfassoc_DLL - Path to wfassoc DLL
# wfassoc_ROOT - The root directory (user-provided)
#
# This module also creates the following imported targets:
# wfassoc::wfassoc - Main wfassoc library (includes both include and link)
#
set(wfassoc_FOUND FALSE)
# Require user to set wfassoc_ROOT
if(NOT wfassoc_ROOT)
message(FATAL_ERROR "wfassoc_ROOT must be set to the installation directory of wfassoc")
endif()
# Check existence of required subdirectories
if(NOT EXISTS ${wfassoc_ROOT})
message(FATAL_ERROR "wfassoc_ROOT directory does not exist: ${wfassoc_ROOT}")
endif()
set(wfassoc_INCLUDE_DIR ${wfassoc_ROOT}/include)
set(wfassoc_LIB_DIR ${wfassoc_ROOT}/lib)
set(wfassoc_BIN_DIR ${wfassoc_ROOT}/bin)
# Find header files
if(EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc.h AND EXISTS ${wfassoc_INCLUDE_DIR}/wfassoc++.h)
set(wfassoc_INCLUDE_DIRS ${wfassoc_INCLUDE_DIR})
else()
message(SEND_ERROR "Missing wfassoc header files in ${wfassoc_INCLUDE_DIR}")
return()
endif()
# Find import library (.lib)
find_file(wfassoc_LIBRARIES
NAMES wfassoc_cdylib.dll.lib
PATHS ${wfassoc_LIB_DIR}
NO_DEFAULT_PATH
DOC "wfassoc import library"
)
if(NOT wfassoc_LIBRARIES)
message(SEND_ERROR "Missing wfassoc import library (wfassoc_cdylib.dll.lib) in ${wfassoc_LIB_DIR}")
return()
endif()
# Find DLL file
find_file(wfassoc_DLL
NAMES wfassoc_cdylib.dll
PATHS ${wfassoc_BIN_DIR}
NO_DEFAULT_PATH
DOC "wfassoc dynamic library"
)
if(NOT wfassoc_DLL)
message(SEND_ERROR "Missing wfassoc DLL (wfassoc_cdylib.dll) in ${wfassoc_BIN_DIR}")
return()
endif()
# Everything found
set(wfassoc_FOUND TRUE)
# Mark variables as advanced for ccmake/cmake-gui
mark_as_advanced(wfassoc_INCLUDE_DIRS wfassoc_LIBRARIES wfassoc_DLL)
# Create imported target for wfassoc
if(wfassoc_FOUND AND NOT TARGET wfassoc::wfassoc)
add_library(wfassoc::wfassoc SHARED IMPORTED)
# Set include directories
set_target_properties(wfassoc::wfassoc PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES ${wfassoc_INCLUDE_DIRS}
)
# Set import library location
set_target_properties(wfassoc::wfassoc PROPERTIES
IMPORTED_IMPLIB "${wfassoc_LIBRARIES}"
IMPORTED_LOCATION "${wfassoc_DLL}"
)
endif()
# Optional: Print status message
if(wfassoc_FOUND)
message(STATUS "Found wfassoc:")
message(STATUS " Root : ${wfassoc_ROOT}")
message(STATUS " Include : ${wfassoc_INCLUDE_DIRS}")
message(STATUS " Library : ${wfassoc_LIBRARIES}")
message(STATUS " DLL : ${wfassoc_DLL}")
endif()

View File

@@ -0,0 +1,290 @@
/**
* @file wfassoc++.h
* @brief Windows File Association C++ API header
*
* This header provides C++ API for managing Windows file associations,
* based on its C-compatible API.
* The API is designed to work with at least C++17.
*/
#pragma once
#ifndef WFASSOCPP_H_
#define WFASSOCPP_H_
#include "wfassoc.h"
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
namespace wfassocpp {
using wfassoc::CStyleString;
using wfassoc::Token;
using wfassoc::HICON;
using wfassoc::INVALID_HICON;
using wfassoc::INVALID_INDEX;
using wfassoc::Scope;
using wfassoc::View;
/// @private
inline void _Check(bool result) {
if (!result) {
throw std::runtime_error(wfassoc::WFGetLastError());
}
}
/// @private
inline Token _INVALID_TOKEN() {
static Token v = wfassoc::WFInvalidToken();
return v;
}
class Schema {
public:
Schema() { _Check(wfassoc::WFSchemaCreate(&_token)); }
~Schema() {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFSchemaDestroy(_token);
}
}
Schema(const Schema&) = delete;
Schema& operator=(const Schema&) = delete;
Schema(Schema&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
Schema& operator=(Schema&& other) noexcept {
if (this != &other) {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFSchemaDestroy(_token);
}
_token = other._token;
other._token = _INVALID_TOKEN();
}
return *this;
}
void SetIdentifier(const char* value) { _Check(wfassoc::WFSchemaSetIdentifier(_token, value)); }
void SetPath(const char* value) { _Check(wfassoc::WFSchemaSetPath(_token, value)); }
void SetClsid(const char* value) { _Check(wfassoc::WFSchemaSetClsid(_token, value)); }
void SetName(const char* value) { _Check(wfassoc::WFSchemaSetName(_token, value)); }
void SetIcon(const char* value) { _Check(wfassoc::WFSchemaSetIcon(_token, value)); }
void SetBehavior(const char* value) { _Check(wfassoc::WFSchemaSetBehavior(_token, value)); }
void AddStr(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddStr(_token, name, value)); }
void AddIcon(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddIcon(_token, name, value)); }
void AddBehavior(const char* name, const char* value) { _Check(wfassoc::WFSchemaAddBehavior(_token, name, value)); }
void AddExt(const char* ext, const char* ext_name, const char* ext_icon, const char* ext_behavior) {
_Check(wfassoc::WFSchemaAddExt(_token, ext, ext_name, ext_icon, ext_behavior));
}
private:
friend class Program;
/// @private
Token Release() noexcept {
Token t = _token;
_token = _INVALID_TOKEN();
return t;
}
Token _token;
};
class IconRc {
public:
explicit IconRc(Token token) : _token(token) {}
~IconRc() {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFIconRcDestroy(_token);
}
}
IconRc(const IconRc&) = delete;
IconRc& operator=(const IconRc&) = delete;
IconRc(IconRc&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
IconRc& operator=(IconRc&& other) noexcept {
if (this != &other) {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFIconRcDestroy(_token);
}
_token = other._token;
other._token = _INVALID_TOKEN();
}
return *this;
}
HICON GetIcon() {
HICON icon = nullptr;
_Check(wfassoc::WFIconRcGetIcon(_token, &icon));
return icon;
}
private:
Token _token;
};
class ExtStatus {
public:
explicit ExtStatus(Token token) : _token(token) {}
~ExtStatus() {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFExtStatusDestroy(_token);
}
}
ExtStatus(const ExtStatus&) = delete;
ExtStatus& operator=(const ExtStatus&) = delete;
ExtStatus(ExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
ExtStatus& operator=(ExtStatus&& other) noexcept {
if (this != &other) {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFExtStatusDestroy(_token);
}
_token = other._token;
other._token = _INVALID_TOKEN();
}
return *this;
}
std::string GetName() {
const char* name = nullptr;
_Check(wfassoc::WFExtStatusGetName(_token, &name));
return std::string(name);
}
HICON GetIcon() {
HICON icon = nullptr;
_Check(wfassoc::WFExtStatusGetIcon(_token, &icon));
return icon;
}
private:
Token _token;
};
class SelfExtStatus {
public:
explicit SelfExtStatus(Token token) : _token(token) {}
~SelfExtStatus() {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFSelfExtStatusDestroy(_token);
}
}
SelfExtStatus(const SelfExtStatus&) = delete;
SelfExtStatus& operator=(const SelfExtStatus&) = delete;
SelfExtStatus(SelfExtStatus&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
SelfExtStatus& operator=(SelfExtStatus&& other) noexcept {
if (this != &other) {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFSelfExtStatusDestroy(_token);
}
_token = other._token;
other._token = _INVALID_TOKEN();
}
return *this;
}
std::string GetName() {
const char* name = nullptr;
_Check(wfassoc::WFSelfExtStatusGetName(_token, &name));
return std::string(name);
}
HICON GetIcon() {
HICON icon = nullptr;
_Check(wfassoc::WFSelfExtStatusGetIcon(_token, &icon));
return icon;
}
std::string GetExt() {
const char* inner = nullptr;
_Check(wfassoc::WFSelfExtStatusGetExt(_token, &inner));
return std::string(inner);
}
std::string GetDottedExt() {
const char* inner = nullptr;
_Check(wfassoc::WFSelfExtStatusGetDottedExt(_token, &inner));
return std::string(inner);
}
private:
Token _token;
};
class Program {
public:
explicit Program(Schema&& schema) {
_Check(wfassoc::WFProgramCreate(schema.Release(), &_token));
}
~Program() {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFProgramDestroy(_token);
}
}
Program(const Program&) = delete;
Program& operator=(const Program&) = delete;
Program(Program&& other) noexcept : _token(other._token) { other._token = _INVALID_TOKEN(); }
Program& operator=(Program&& other) noexcept {
if (this != &other) {
if (_token != _INVALID_TOKEN()) {
wfassoc::WFProgramDestroy(_token);
}
_token = other._token;
other._token = _INVALID_TOKEN();
}
return *this;
}
std::string ResolveName() {
const char* name = nullptr;
_Check(wfassoc::WFProgramResolveName(_token, &name));
return std::string(name);
}
IconRc ResolveIcon() {
Token token = _INVALID_TOKEN();
_Check(wfassoc::WFProgramResolveIcon(_token, &token));
return IconRc(token);
}
size_t ExtsLen() {
size_t len = 0;
_Check(wfassoc::WFProgramExtsLen(_token, &len));
return len;
}
size_t FindExt(const char* body) {
size_t index = INVALID_INDEX;
_Check(wfassoc::WFProgramFindExt(_token, body, &index));
return index;
}
SelfExtStatus ResolveExt(size_t index) {
Token token = _INVALID_TOKEN();
_Check(wfassoc::WFProgramResolveExt(_token, index, &token));
return SelfExtStatus(token);
}
void Register(Scope scope) { _Check(wfassoc::WFProgramRegister(_token, scope)); }
void Unregister(Scope scope) { _Check(wfassoc::WFProgramUnregister(_token, scope)); }
bool IsRegistered(Scope scope) {
bool result = false;
_Check(wfassoc::WFProgramIsRegistered(_token, scope, &result));
return result;
}
void LinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramLinkExt(_token, scope, index)); }
void UnlinkExt(Scope scope, size_t index) { _Check(wfassoc::WFProgramUnlinkExt(_token, scope, index)); }
std::optional<ExtStatus> QueryExt(View view, size_t index) {
Token token = _INVALID_TOKEN();
_Check(wfassoc::WFProgramQueryExt(_token, view, index, &token));
if (token == _INVALID_TOKEN()) {
return std::nullopt;
}
return ExtStatus(token);
}
private:
Token _token;
};
} // namespace wfassocpp
#endif // WFASSOCPP_H_

View File

@@ -4,12 +4,12 @@
*
* This header provides a C-compatible API for managing Windows file associations,
* including schema creation, program registration, and extension management.
* The API is designed to work with both C and C++ compilers.
* The API is designed to at least work with both C99 and C++17 compilers.
*/
#pragma once
#ifndef __WFASSOC_H__
#define __WFASSOC_H__
#ifndef WFASSOC_H_
#define WFASSOC_H_
#ifdef __cplusplus
#include <cstddef>
@@ -136,6 +136,9 @@ bool WFShutdown(void);
*
* The execution of this function do not need to be wrapped by WFStartup() and WFShutdown().
*
* The string this function return use different buffer with function return string value.
* So you don't worry about that calling this function may invalidate function function return string value.
*
* @return Null-terminated UTF-8 string containing the error message.
* If no error has occurred, the string is empty.
* There is no possibility of a NULL return value.
@@ -315,6 +318,9 @@ bool WFSchemaAddExt(Token in_schema,
* It means that the Schema object cannot be used after this call.
* And you do not need to call WFSchemaDestroy() for this Schema object after this call.
*
* Please note that the given Schema object will always be consumed,
* no matter this function return success or failure.
*
* @param[in] in_schema Schema token (will be consumed)
* @param[out] out_program Pointer to receive the Program token.
* The receiver take the ownership of this Program object.
@@ -336,8 +342,13 @@ bool WFProgramDestroy(Token in_program);
/**
* @brief Resolve the provided program name of this Program
*
* The name will be user specified first,
* then fallback to program manifest file specified name,
* and finally fallback to the file name of executable.
*
* @param[in] in_program Program token
* @param[out] out_name Pointer to receive the resolved name, or NULL if not found.
* @param[out] out_name Pointer to receive the resolved name.
* There is no possibility that this value is NULL.
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
@@ -346,8 +357,12 @@ bool WFProgramResolveName(Token in_program, CStyleString *out_name);
/**
* @brief Resolve the Program icon resource
*
* The icon will be user specified first,
* the fallback to the first icon of program,
* and finally fallback to the system default executable icon.
*
* @param[in] in_program Program token
* @param[out] out_icon_rc Pointer to receive the icon resource token, or invalid token if not found.
* @param[out] out_icon_rc Pointer to receive the icon resource token.
* The caller take the ownership of created icon resource object.
* And it should be freed by calling WFIconRcDestroy() when it is no longer needed.
* @return true on success, false on failure
@@ -363,18 +378,6 @@ bool WFProgramResolveIcon(Token in_program, Token *out_icon_rc);
*/
bool WFProgramExtsLen(Token in_program, size_t *out_len);
/**
* @brief Get a file extension by index
*
* @param[in] in_program Program token
* @param[in] in_index Index of the extension to retrieve
* @param[out] out_ext Pointer to receive the file extension token.
* The caller take the ownership of created file extension object.
* And it should be freed by calling WFExtDestroy() when it is no longer needed.
* @return true on success, false on failure
*/
bool WFProgramGetExt(Token in_program, size_t in_index, Token *out_ext);
/**
* @brief Find a file extension by its body (extension string)
*
@@ -385,6 +388,18 @@ bool WFProgramGetExt(Token in_program, size_t in_index, Token *out_ext);
*/
bool WFProgramFindExt(Token in_program, CStyleString in_body, size_t *out_index);
/**
* @brief Resolve this program provided extension's details by index
*
* @param[in] in_program Program token
* @param[in] in_index Index of the extension to resolve
* @param[out] out_self_ext_status Pointer to receive the self extension status token.
* The caller take the ownership of created self extension status object.
* And it should be freed by calling WFSelfExtStatusDestroy() when it is no longer needed.
* @return true on success, false on failure
*/
bool WFProgramResolveExt(Token in_program, size_t in_index, Token *out_self_ext_status);
/**
* @brief Register the Program in the specified scope
*
@@ -457,7 +472,10 @@ bool WFProgramQueryExt(Token in_program, View in_view, size_t in_index, Token *o
bool WFExtStatusDestroy(Token in_ext_status);
/**
* @brief Get the name from an extension status object
* @brief Get the display name from an extension status object
*
* The display will be user specified first,
* the fallback to its ProgId verbatim.
*
* @param[in] in_ext_status Extension status token
* @param[out] out_name Pointer to receive the name.
@@ -471,14 +489,75 @@ bool WFExtStatusGetName(Token in_ext_status, CStyleString *out_name);
/**
* @brief Get the icon from an extension status object
*
* The icon will be user specified first,
* the fallback to the system default file icon.
*
* @param[in] in_ext_status Extension status token
* @param[out] out_icon Pointer to receive the icon handle, or INVALID_HICON if not available.
* @param[out] out_icon Pointer to receive the icon handle.
* This icon handle will be freed once this icon resource object is destroyed.
* Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
bool WFExtStatusGetIcon(Token in_ext_status, HICON *out_icon);
/**
* @brief Destroy a self extension status object
*
* @param[in] in_self_ext_status Self extension status token to destroy
* @return true on success, false on failure
*/
bool WFSelfExtStatusDestroy(Token in_self_ext_status);
/**
* @brief Get the display name from a self extension status object
*
* The display will be user specified first,
* the fallback to its ProgId verbatim.
*
* @param[in] in_self_ext_status Self extension status token
* @param[out] out_name Pointer to receive the name string.
* There is no possibility that this value is NULL.
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
bool WFSelfExtStatusGetName(Token in_self_ext_status, CStyleString *out_name);
/**
* @brief Get the icon from a self extension status object
*
* The icon will be user specified first,
* the fallback to the system default file icon.
*
* @param[in] in_self_ext_status Self extension status token
* @param[out] out_icon Pointer to receive the icon handle.
* This icon handle will be freed once this self extension status object is destroyed.
* Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
bool WFSelfExtStatusGetIcon(Token in_self_ext_status, HICON *out_icon);
/**
* @brief Get the extension string (without leading dot) from a self extension status object
*
* @param[in] in_self_ext_status Self extension status token
* @param[out] out_inner Pointer to receive the file extension name (without leading dot).
* There is no possibility that this value is NULL.
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
bool WFSelfExtStatusGetExt(Token in_self_ext_status, CStyleString *out_inner);
/**
* @brief Get the dotted extension string (with leading dot) from a self extension status object
*
* @param[in] in_self_ext_status Self extension status token
* @param[out] out_inner Pointer to receive the file extension string (with leading dot).
* There is no possibility that this value is NULL.
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
bool WFSelfExtStatusGetDottedExt(Token in_self_ext_status, CStyleString *out_inner);
/**
* @brief Destroy an icon resource object
*
@@ -499,36 +578,6 @@ bool WFIconRcDestroy(Token in_icon_rc);
*/
bool WFIconRcGetIcon(Token in_icon_rc, HICON *out_icon);
/**
* @brief Destroy a file extension object
*
* @param[in] in_ext Extension token to destroy
* @return true on success, false on failure
*/
bool WFExtDestroy(Token in_ext);
/**
* @brief Get the inner extension string without dot
*
* @param[in] in_ext Extension token
* @param[out] out_inner Pointer to receive the file extension name (without leading dot).
* There is no possibility that this value is NULL.
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
bool WFExtGetInner(Token in_ext, CStyleString *out_inner);
/**
* @brief Get the inner extension string with dot prefix
*
* @param[in] in_ext Extension token
* @param[out] out_inner Pointer to receive the file extension string (with leading dot).
* There is no possibility that this value is NULL.
* This string will be freed at the next API call. Please make a copy immediately if you need to use it longer.
* @return true on success, false on failure
*/
bool WFExtGetDottedInner(Token in_ext, CStyleString *out_inner);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@@ -537,4 +586,4 @@ bool WFExtGetDottedInner(Token in_ext, CStyleString *out_inner);
} // namespace wfassoc
#endif // __cplusplus
#endif // __WFASSOC_H__
#endif // WFASSOC_H_

View File

@@ -24,17 +24,17 @@ enum Error {
Program(#[from] wfassoc::highlevel::ProgramError),
/// Error when manipulating with C-style string.
#[error("{0}")]
#[error("C-Style string FFI error:{0}")]
CStrFfi(#[from] cstr_ffi::Error),
/// Error when manipulating with object pool.
#[error("{0}")]
#[error("object pool error: {0}")]
ObjectPool(#[from] object_pool::Error),
/// Error occurs when checking enum value
#[error("")]
#[error("the enumeration value provided to FFI function is out of its range")]
EnumOutOfRange,
/// Error when manipulating with poison RwLock
#[error("RwLock is poisoning")]
#[error("concurrency error: RwLock is poisonous")]
PoisonRwLock,
}
@@ -191,10 +191,11 @@ static PROGRAM_POOL: LazyLock<RwLock<ObjectPool<Program>>> =
static EXT_STATUS_POOL: LazyLock<RwLock<ObjectPool<wfassoc::highlevel::ProgramExtStatus>>> =
LazyLock::new(|| RwLock::new(ObjectPool::new()));
static ICON_RC_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::IconRc>>> =
LazyLock::new(|| RwLock::new(ObjectPool::new()));
static SELF_EXT_STATUS_POOL: LazyLock<
RwLock<ObjectPool<wfassoc::highlevel::ProgramSelfExtStatus>>,
> = LazyLock::new(|| RwLock::new(ObjectPool::new()));
static EXT_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::Ext>>> =
static ICON_RC_POOL: LazyLock<RwLock<ObjectPool<wfassoc::win32::concept::IconRc>>> =
LazyLock::new(|| RwLock::new(ObjectPool::new()));
// endregion
@@ -218,8 +219,8 @@ pub extern "C" fn WFStartup() -> bool {
let _pool = pull_writer!(SCHEMA_POOL)?;
let _pool = pull_writer!(PROGRAM_POOL)?;
let _pool = pull_writer!(EXT_STATUS_POOL)?;
let _pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
let _pool = pull_writer!(ICON_RC_POOL)?;
let _pool = pull_writer!(EXT_POOL)?;
Ok(())
})
}
@@ -234,9 +235,9 @@ pub extern "C" fn WFShutdown() -> bool {
pool.clear();
let mut pool = pull_writer!(EXT_STATUS_POOL)?;
pool.clear();
let mut pool = pull_writer!(ICON_RC_POOL)?;
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
pool.clear();
let mut pool = pull_writer!(EXT_POOL)?;
let mut pool = pull_writer!(ICON_RC_POOL)?;
pool.clear();
Ok(())
})
@@ -491,16 +492,9 @@ pub extern "C" fn WFProgramResolveName(
let mut pool = pull_writer!(PROGRAM_POOL)?;
let program = pool.get_mut(in_program)?;
let name = match program.resolve_name()? {
Some(name) => {
let name = program.resolve_name()?;
cstr_ffi::set_ffi_string(&name)?;
cstr_ffi::get_ffi_string()
},
None => {
std::ptr::null()
},
};
Ok(name)
Ok(cstr_ffi::get_ffi_string())
})
}
@@ -514,16 +508,8 @@ pub extern "C" fn WFProgramResolveIcon(
let program = pool.get_mut(in_program)?;
let icon = program.resolve_icon()?;
let token = match icon {
Some(icon) => {
let mut pool = pull_writer!(ICON_RC_POOL)?;
pool.allocate(icon)?
},
None => {
object_pool::invalid_token()
},
};
Ok(token)
Ok(pool.allocate(icon)?)
})
}
@@ -539,23 +525,6 @@ pub extern "C" fn WFProgramExtsLen(
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFProgramGetExt(
in_program: in_param_ty!(Token),
in_index: in_param_ty!(usize),
out_ext: out_param_ty!(Token),
) -> bool {
cffi_wrapper!(|in_program: Token, in_index: usize| -> (out_ext: Token) {
let mut pool = pull_writer!(PROGRAM_POOL)?;
let program = pool.get_mut(in_program)?;
let ext = program.get_ext(in_index)?;
let mut pool = pull_writer!(EXT_POOL)?;
let token = pool.allocate(ext.clone())?;
Ok(token)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFProgramFindExt(
in_program: in_param_ty!(Token),
@@ -575,6 +544,23 @@ pub extern "C" fn WFProgramFindExt(
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFProgramResolveExt(
in_program: in_param_ty!(Token),
in_index: in_param_ty!(usize),
out_self_ext_status: out_param_ty!(Token),
) -> bool {
cffi_wrapper!(|in_program: Token, in_index: usize| -> (out_self_ext_status: Token) {
let mut pool = pull_writer!(PROGRAM_POOL)?;
let program = pool.get_mut(in_program)?;
let self_ext_status = program.resolve_ext(in_index)?;
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
let token = pool.allocate(self_ext_status)?;
Ok(token)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFProgramRegister(
in_program: in_param_ty!(Token),
@@ -706,11 +692,76 @@ pub extern "C" fn WFExtStatusGetIcon(
let pool = pull_reader!(EXT_STATUS_POOL)?;
let ext_status = pool.get(in_ext_status)?;
let icon = match ext_status.get_icon() {
Some(icon) => icon.get_icon(),
None => ffi_types::INVALID_HICON,
};
Ok(icon)
let icon = ext_status.get_icon();
Ok(icon.get_icon())
})
}
// endregion
// region: Self Extension Status
#[unsafe(no_mangle)]
pub extern "C" fn WFSelfExtStatusDestroy(in_self_ext_status: in_param_ty!(Token)) -> bool {
cffi_wrapper!(|in_self_ext_status: Token| {
let mut pool = pull_writer!(SELF_EXT_STATUS_POOL)?;
Ok(pool.free(in_self_ext_status)?)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFSelfExtStatusGetName(
in_self_ext_status: in_param_ty!(Token),
out_name: out_param_ty!(CStyleString),
) -> bool {
cffi_wrapper!(|in_self_ext_status: Token| -> (out_name: CStyleString) {
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
let self_ext_status = pool.get(in_self_ext_status)?;
cstr_ffi::set_ffi_string(self_ext_status.get_name())?;
Ok(cstr_ffi::get_ffi_string())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFSelfExtStatusGetIcon(
in_self_ext_status: in_param_ty!(Token),
out_icon: out_param_ty!(HICON),
) -> bool {
cffi_wrapper!(|in_self_ext_status: Token| -> (out_icon: HICON) {
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
let self_ext_status = pool.get(in_self_ext_status)?;
let icon = self_ext_status.get_icon();
Ok(icon.get_icon())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFSelfExtStatusGetExt(
in_self_ext_status: in_param_ty!(Token),
out_inner: out_param_ty!(CStyleString),
) -> bool {
cffi_wrapper!(|in_self_ext_status: Token| -> (out_inner: CStyleString) {
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
let self_ext_status = pool.get(in_self_ext_status)?;
cstr_ffi::set_ffi_string(self_ext_status.get_ext())?;
Ok(cstr_ffi::get_ffi_string())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFSelfExtStatusGetDottedExt(
in_self_ext_status: in_param_ty!(Token),
out_inner: out_param_ty!(CStyleString),
) -> bool {
cffi_wrapper!(|in_self_ext_status: Token| -> (out_inner: CStyleString) {
let pool = pull_reader!(SELF_EXT_STATUS_POOL)?;
let self_ext_status = pool.get(in_self_ext_status)?;
cstr_ffi::set_ffi_string(self_ext_status.get_dotted_ext().as_str())?;
Ok(cstr_ffi::get_ffi_string())
})
}
@@ -740,41 +791,3 @@ pub extern "C" fn WFIconRcGetIcon(
}
// endregion
// region: File Extension
#[unsafe(no_mangle)]
pub extern "C" fn WFExtDestroy(in_ext: in_param_ty!(Token)) -> bool {
cffi_wrapper!(|in_ext: Token| {
let mut pool = pull_writer!(EXT_POOL)?;
Ok(pool.free(in_ext)?)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFExtGetInner(
in_ext: in_param_ty!(Token),
out_inner: out_param_ty!(CStyleString),
) -> bool {
cffi_wrapper!(|in_ext: Token| -> (out_inner: CStyleString) {
let pool = pull_reader!(EXT_POOL)?;
let ext = pool.get(in_ext)?;
cstr_ffi::set_ffi_string(ext.inner())?;
Ok(cstr_ffi::get_ffi_string())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn WFExtGetDottedInner(
in_ext: in_param_ty!(Token),
out_inner: out_param_ty!(CStyleString),
) -> bool {
cffi_wrapper!(|in_ext: Token| -> (out_inner: CStyleString) {
let pool = pull_reader!(EXT_POOL)?;
let ext = pool.get(in_ext)?;
cstr_ffi::set_ffi_string(&ext.dotted_inner())?;
Ok(cstr_ffi::get_ffi_string())
})
}
// endregion

View File

@@ -139,9 +139,12 @@ fn run_ext_list(
// Fetch info
let mut ext_list: HashMap<String, Option<String>> = HashMap::new();
for index in 0..program.exts_len() {
let ext = program.get_ext(index)?;
let self_ext_status = program.resolve_ext(index)?;
let status = program.query_ext(view, index)?;
ext_list.insert(ext.dotted_inner(), status.map(|s| s.get_name().to_string()));
ext_list.insert(
self_ext_status.get_dotted_ext(),
status.map(|s| s.get_name().to_string()),
);
}
// Output by styles

View File

@@ -1,5 +1,7 @@
# WFassoc Core
## Usage
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.
So programmers can directly use them.
@@ -10,3 +12,21 @@ Oppositely, for visiting low level API, please use `lowlevel` module.
If you are a programmer who want to take a deep into the internal implementations,
see `win32` module and its submodules for detail.
## Test Notes
Some tests of this crate may be dangerous because they need to manipulate Windows Registry.
So it is highlt recommend that run these tests in sandbox environment.
In detailed words, you should run `cargo test --no-run` to build all test first,
then fetch the path to executable tests according to this command shown on console.
Then execute these executable tests in your sandbox for testing this crate.
Additionally, some tests also need Administration permission for testing,
because it requires write permission in HKLM.
If you do not test it with sandbox and administrative environment,
test program will assert paniked and tell you how to resolve these issues.
The reason why do not run `cargo test` in sandbox environment directly,
is that `cargo` can not find built tests located in host machine.
It will try to fetch all dependencies again and rebuild test in sandbox entirely.
So we use this complex way for testing.

View File

@@ -27,7 +27,7 @@ mod schema;
mod program;
pub use schema::{Schema, SchemaError};
pub use program::{Program, ParseProgramError, ProgramError, ProgramExtStatus};
pub use program::{Program, ParseProgramError, ProgramError, ProgramSelfExtStatus, ProgramExtStatus};
pub use lowlevel::{Scope, View};
// endregion

View File

@@ -44,6 +44,8 @@ pub enum ParseProgramError {
pub enum ProgramError {
#[error("{0}")]
Lowlevel(#[from] lowlevel::Error),
#[error("{0}")]
LoadIconRc(#[from] concept::LoadIconRcError),
#[error("given index is invalid")]
BadIndex,
}
@@ -53,10 +55,12 @@ pub enum ProgramError {
// region: Program
/// Program is a complete and immutable program representer
#[derive(Debug)]
pub struct Program {
app_paths_key: lowlevel::AppPathsKey,
applications_key: lowlevel::ApplicationsKey,
app_path: String,
app_file_name: String,
app_dir_path: String,
name: Option<Arc<ProgramStr>>,
icon: Option<Arc<ProgramIcon>>,
@@ -254,6 +258,7 @@ impl Program {
app_paths_key,
applications_key,
app_path,
app_file_name,
app_dir_path,
name,
icon,
@@ -268,39 +273,80 @@ impl Program {
}
impl Program {
pub fn resolve_name(&self) -> Result<Option<String>, ProgramError> {
Ok(self
pub fn resolve_name(&self) -> Result<String, ProgramError> {
// Fecch from user specified name first
let name = self
.name
.as_ref()
.map(|name| name.inner.extract().ok())
.flatten())
.flatten();
if let Some(name) = name {
return Ok(name);
}
pub fn resolve_icon(&self) -> Result<Option<concept::IconRc>, ProgramError> {
Ok(self
// TODO:
// Fetch it from executable manifest file.
// Finally fallback to use executable name.
Ok(self.app_file_name.clone())
}
pub fn resolve_icon(&self) -> Result<concept::IconRc, ProgramError> {
// Fetch from user specified icon first
let icon = self
.icon
.as_ref()
.map(|icon| icon.inner.extract(concept::IconSizeKind::Small).ok())
.flatten())
.flatten();
if let Some(icon) = icon {
return Ok(icon);
}
// Fetch from the first icon of executable instead.
let icon = concept::IconRc::new(&self.app_path, 0, concept::IconSizeKind::Small).ok();
if let Some(icon) = icon {
return Ok(icon);
}
// Finally fallback to use system default executable icon.
Ok(concept::IconRc::GENERIC_APPLICATION(
concept::IconSizeKind::Small,
)?)
}
pub fn exts_len(&self) -> usize {
self.ext_keys.len()
}
pub fn get_ext(&self, index: usize) -> Result<&concept::Ext, ProgramError> {
match self.ext_keys.get(index) {
Some(program_key) => {
let ext_key = &program_key.ext_key;
Ok(ext_key.inner())
}
None => Err(ProgramError::BadIndex),
}
}
pub fn find_ext(&self, body: &str) -> Option<usize> {
self.ext_keys_map.get(body).copied()
}
pub fn resolve_ext(&self, index: usize) -> Result<ProgramSelfExtStatus, ProgramError> {
// Fetch data
let progid_ext_key = self.ext_keys.get(index).ok_or(ProgramError::BadIndex)?;
// Try resolving name with string resource first,
// and fallback to ProgId verbatim.
let name = progid_ext_key
.name
.inner
.extract()
.unwrap_or(progid_ext_key.progid_key.inner().to_string());
// Try to fetch icon, and fallback to system default file icon.
let icon = progid_ext_key
.icon
.inner
.extract(concept::IconSizeKind::Small)
.unwrap_or(concept::IconRc::GENERIC_DOCUMENT(
concept::IconSizeKind::Small,
)?);
// Okey, return it
Ok(ProgramSelfExtStatus::new(
progid_ext_key.ext_key.inner().clone(),
name,
icon,
))
}
}
impl Program {
@@ -356,6 +402,7 @@ impl Program {
// Add this progid to file extension "open with" list.
let ext_key = &mut program_key.ext_key;
ext_key.ensure(scope)?;
ext_key.add_into_open_with_progids(scope, progid_key.inner())?;
}
@@ -392,9 +439,12 @@ impl Program {
// there is no need to reset the default open way of file extension.
// So we simply remove it from "open with" list.
// Remove this ProgId from file extension "open with" list.
// Remove this ProgId from file extension "open with" list,
// if this file extension is existing
let ext_key = &mut program_key.ext_key;
if ext_key.is_exist(scope.into())? {
ext_key.remove_from_open_with_progids(scope, progid_key.inner())?;
}
// Delete ProgId subkey
progid_key.delete(scope)?;
@@ -535,11 +585,14 @@ impl Program {
.flatten();
}
let name = name.unwrap_or(progid_key.inner().to_string());
// Now try to fetch icon.
// Now try to fetch icon and fallback to system default file icon.
let icon = progid_key
.get_default_icon(view)?
.map(|ico| ico.extract(concept::IconSizeKind::Small).ok())
.flatten();
.flatten()
.unwrap_or(concept::IconRc::GENERIC_DOCUMENT(
concept::IconSizeKind::Small,
)?);
// Okey, return it.
Ok(Some(ProgramExtStatus::new(name, icon)))
@@ -589,16 +642,58 @@ struct ProgramProgIdExtKey {
// region: Exposed Stuff
/// Exposed struct representing this program provided method for opening specific file extension.
///
/// The data including the extension name, diaplay name and icon.
#[derive(Debug)]
pub struct ProgramSelfExtStatus {
ext: concept::Ext,
name: String,
icon: concept::IconRc,
}
impl ProgramSelfExtStatus {
fn new(ext: concept::Ext, name: String, icon: concept::IconRc) -> Self {
Self { ext, name, icon }
}
/// Get the extension name without leading dot like `jpg`.
pub fn get_ext(&self) -> &str {
self.ext.inner()
}
/// Get the extension name with leading dot like `.jpg`.
pub fn get_dotted_ext(&self) -> String {
self.ext.dotted_inner()
}
/// Get the display name of this program.
///
/// The program provided display name will be used firstly.
/// If this program has no display name, the stringified ProgId will be used instead.
pub fn get_name(&self) -> &str {
self.name.as_str()
}
/// Get the icon of this program.
///
/// Due to the icon is optional, if there is no icon, return None.
pub fn get_icon(&self) -> &concept::IconRc {
&self.icon
}
}
/// Exposed struct representing the default associated program of specific file extension.
///
/// The data including the diaplay name and icon.
#[derive(Debug)]
pub struct ProgramExtStatus {
name: String,
icon: Option<concept::IconRc>,
icon: concept::IconRc,
}
impl ProgramExtStatus {
fn new(name: String, icon: Option<concept::IconRc>) -> Self {
fn new(name: String, icon: concept::IconRc) -> Self {
Self { name, icon }
}
@@ -613,8 +708,8 @@ impl ProgramExtStatus {
/// Get the icon of this program.
///
/// Due to the icon is optional, if there is no icon, return None.
pub fn get_icon(&self) -> Option<&concept::IconRc> {
self.icon.as_ref()
pub fn get_icon(&self) -> &concept::IconRc {
&self.icon
}
}

View File

@@ -156,7 +156,7 @@ impl Schema {
}
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())
}
pub(super) fn get_strs(&self) -> &HashMap<String, String> {

View File

@@ -62,7 +62,7 @@ impl AppPathsKey {
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,
/// otherwise false indicating there already is an existing key.
@@ -79,15 +79,16 @@ 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,
/// this function does nothing.
pub fn delete(&mut self, scope: Scope) -> Result<()> {
/// 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)?;
key.parent_key
.delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?;
Ok(())
Ok(regext::arbitrarily_delete_subkey_all(
&key.parent_key,
regext::blank_path_guard(self.key_name.inner())?,
)?)
}
fn open_scope_for_getter(&self, scope: Scope) -> Result<RegKey> {

View File

@@ -68,6 +68,10 @@ impl ApplicationsKey {
Ok(key.is_some())
}
/// Ensure this application key is presented in Applications key.
///
/// Return true if we newly create this key,
/// otherwise false indicating there already is an existing key.
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
let key = self.open_scope_for_write(scope)?;
if let None = key.this_key {
@@ -81,11 +85,16 @@ 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)?;
key.parent_key
.delete_subkey_all(regext::blank_path_guard(self.key_name.inner())?)?;
Ok(())
Ok(regext::arbitrarily_delete_subkey_all(
&key.parent_key,
regext::blank_path_guard(self.key_name.inner())?,
)?)
}
// YYC MARK:
@@ -176,7 +185,7 @@ impl ApplicationsKey {
}
None => {
// 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)?;
}
}
@@ -217,7 +226,7 @@ impl ApplicationsKey {
}
None => {
// 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)?;
}
}
@@ -248,7 +257,7 @@ impl ApplicationsKey {
}
None => {
// Delete this key
key.delete_value(Self::NAMEOF_FRIENDLY_APP_NAME)?;
regext::arbitrarily_delete_value(&key, Self::NAMEOF_FRIENDLY_APP_NAME)?;
}
}
@@ -298,7 +307,7 @@ impl ApplicationsKey {
}
None => {
// Delete this subkey.
key.delete_subkey_all(Self::NAMEOF_SUPPORTED_TYPES)?;
regext::arbitrarily_delete_subkey_all(&key, Self::NAMEOF_SUPPORTED_TYPES)?;
}
}
@@ -320,7 +329,7 @@ impl ApplicationsKey {
if flag {
key.set_value(Self::NAMEOF_NO_OPEN_WITH, &"")?;
} else {
key.delete_value(Self::NAMEOF_NO_OPEN_WITH)?;
regext::arbitrarily_delete_value(&key, Self::NAMEOF_NO_OPEN_WITH)?;
}
Ok(())
}

View File

@@ -66,6 +66,10 @@ impl ExtKey {
Ok(key.is_some())
}
/// Ensure this file extension key is presented in Classes key.
///
/// Return true if we newly create this key,
/// otherwise false indicating there already is an existing key.
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
let key = self.open_scope_for_write(scope)?;
if let None = key.this_key {
@@ -79,11 +83,16 @@ 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)?;
key.parent_key
.delete_subkey_all(regext::blank_path_guard(self.ext.dotted_inner())?)?;
Ok(())
Ok(regext::arbitrarily_delete_subkey_all(
&key.parent_key,
regext::blank_path_guard(self.ext.dotted_inner())?,
)?)
}
// YYC MARK:
@@ -127,7 +136,7 @@ impl ExtKey {
}
None => {
// Delete this key
key.delete_value(Self::NAMEOF_DEFAULT)?;
regext::arbitrarily_delete_value(&key, Self::NAMEOF_DEFAULT)?;
}
}
@@ -205,7 +214,10 @@ impl ExtKey {
None => return Ok(()),
};
// 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(())
}
}

View File

@@ -66,6 +66,10 @@ impl ProgIdKey {
Ok(key.is_some())
}
/// Ensure this ProgId key is presented in Classes key.
///
/// Return true if we newly create this key,
/// otherwise false indicating there already is an existing key.
pub fn ensure(&mut self, scope: Scope) -> Result<bool> {
let key = self.open_scope_for_write(scope)?;
if let None = key.this_key {
@@ -79,11 +83,16 @@ 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)?;
key.parent_key
.delete_subkey_all(regext::blank_path_guard(self.progid.to_string())?)?;
Ok(())
Ok(regext::arbitrarily_delete_subkey_all(
&key.parent_key,
regext::blank_path_guard(self.progid.to_string())?,
)?)
}
// YYC MARK:
@@ -129,7 +138,7 @@ impl ProgIdKey {
}
None => {
// Delete this key
key.delete_value(Self::NAMEOF_DEFAULT)?;
regext::arbitrarily_delete_value(&key, Self::NAMEOF_DEFAULT)?;
}
}
@@ -209,7 +218,7 @@ impl ProgIdKey {
}
None => {
// 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)?;
}
}
@@ -243,7 +252,7 @@ impl ProgIdKey {
}
None => {
// Delete this key
key.delete_value(Self::NAMEOF_FRIENDLY_TYPE_NAME)?;
regext::arbitrarily_delete_value(&key, Self::NAMEOF_FRIENDLY_TYPE_NAME)?;
}
}
@@ -284,7 +293,7 @@ impl ProgIdKey {
}
None => {
// 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)?;
}
}

View File

@@ -510,6 +510,20 @@ pub struct IconRc {
icon: HICON,
}
impl IconRc {
/// Get the icon preset representing generic document.
#[allow(non_snake_case)]
pub fn GENERIC_DOCUMENT(kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
Self::new("shell32.dll", 0, kind)
}
/// Get the icon preset representing generic executable.
#[allow(non_snake_case)]
pub fn GENERIC_APPLICATION(kind: IconSizeKind) -> Result<Self, LoadIconRcError> {
Self::new("imageres.dll", 0, kind)
}
}
impl IconRc {
/// Load icon from executable or `.ico` file.
///
@@ -867,16 +881,19 @@ pub struct Verb {
}
impl Verb {
/// Get the verb representing Open.
#[allow(non_snake_case)]
pub fn OPEN() -> Self {
Verb::new("open").expect("unexpected bad verb")
}
/// Get the verb representing Edit.
#[allow(non_snake_case)]
pub fn EDIT() -> Self {
Verb::new("edit").expect("unexpected bad verb")
}
/// Get the verb representing Play.
#[allow(non_snake_case)]
pub fn PLAY() -> Self {
Verb::new("play").expect("unexpected bad verb")

View File

@@ -61,6 +61,54 @@ pub fn try_get_value<T: FromRegValue, N: AsRef<OsStr>>(
}
}
/// Delete all tree of given path of given key anyway.
///
/// This function was invented to fix the shortcoming of [RegKey::delete_subkey_all].
/// This function always delete given path of given key no matter it is existing.
/// Oppositely, [RegKey::delete_subkey_all] will return error if there is no such path.
///
/// Return true if we successfully delete this key,
/// otherwise false indicating there is no such key (already deleted).
pub fn arbitrarily_delete_subkey_all<P: AsRef<OsStr>>(
regkey: &RegKey,
path: P,
) -> std::io::Result<bool> {
match regkey.delete_subkey_all(path) {
Ok(()) => Ok(true),
Err(e) => match e.raw_os_error() {
Some(errno) => match errno as u32 {
ERROR_FILE_NOT_FOUND => Ok(false),
_ => Err(e),
},
_ => Err(e),
},
}
}
/// Delete given value key of given key anyway.
///
/// This function was invented to fix the shortcoming of [RegKey::delete_value].
/// This function always delete given value key of given key no matter it is existing.
/// Oppositely, [RegKey::delete_value] will return error if there is no such value key.
///
/// Return true if we successfully delete this value key,
/// otherwise false indicating there is no such value key (already deleted).
pub fn arbitrarily_delete_value<N: AsRef<OsStr>>(
regkey: &RegKey,
name: N,
) -> std::io::Result<bool> {
match regkey.delete_value(name) {
Ok(()) => Ok(true),
Err(e) => match e.raw_os_error() {
Some(errno) => match errno as u32 {
ERROR_FILE_NOT_FOUND => Ok(false),
_ => Err(e),
},
_ => Err(e),
},
}
}
/// Get the name of only subkey in given key.
///
/// If there is only one subkey in given key, the return value is its name.
@@ -110,6 +158,9 @@ pub fn get_all_string_subkey_names(regkey: &RegKey) -> std::io::Result<Vec<Strin
/// This is very dangerous and may be used by accident.
/// So I create this to explicitly indicate this behavior and avoid any mis-type in code.
pub fn clean_all_contents(regkey: &RegKey) -> std::io::Result<()> {
// There is no possibility that this key do not existing,
// because what we are cleaning is self content.
// So directly use delete_subkey_all is okey.
regkey.delete_subkey_all("")
}

27
wfassoc/tests/common.rs Normal file
View File

@@ -0,0 +1,27 @@
/// Check whether we are in sandbox.
///
/// # Panics
///
/// Panic if we are not in sandbox (we can not perform dangerous test).
/// Return if we are in sandbox environment.
pub fn check_sandbox() {
assert!(
std::env::var("SANDBOXIE").is_ok(),
concat!(
"Non-sandbox environment detected. ",
"Executing these tests in non-sandbox environment is VERY dangerous. ",
"Please set \"SANDBOXIE\" environment variable to explicitly indicate you are running these tests in sandbox environment."
)
)
}
pub fn check_privilege() {
assert!(
wfassoc::win32::utilities::has_privilege(),
concat!(
"You are running test without privilege. ",
"These tests must be run with some privilege because it need to manipulate Windows Registry. ",
"Please give it privilege in your sandbox environment."
)
)
}

View File

@@ -186,6 +186,9 @@ fn test_str_ref_str() {
#[test]
fn test_icon_rc() {
fn const_tester(icon: Result<IconRc, LoadIconRcError>) {
assert!(icon.is_ok())
}
fn ok_tester(file: &str, index: u32) {
let icon = IconRc::new(file, index, IconSizeKind::Small);
assert!(icon.is_ok())
@@ -195,6 +198,9 @@ fn test_icon_rc() {
assert!(icon.is_err())
}
// Test 2 const value
const_tester(IconRc::GENERIC_APPLICATION(IconSizeKind::Small));
const_tester(IconRc::GENERIC_APPLICATION(IconSizeKind::Small));
// We pick it from "jpegfile" ProgId
ok_tester("imageres.dll", 72);
ok_tester("notepad.exe", 0);

169
wfassoc/tests/highlevel.rs Normal file
View File

@@ -0,0 +1,169 @@
use wfassoc::highlevel::*;
mod common;
static IDENTIFIER: &str = "Passoc";
static APP_PATH: &str = r"C:\Passoc\passoc.exe";
static CLSID: &str = "{59031a47-3f72-44a7-89c5-5595fe6b30ee}";
static EXT_BODY: &str = "pacfg";
fn make_valid_schema() -> Schema {
let mut schema = Schema::new();
schema.set_identifier(IDENTIFIER);
schema.set_path(APP_PATH);
schema.set_clsid(CLSID);
schema.add_str("main_name", "Passoc Application").unwrap();
schema.add_str("ext_name", "Pacfg File").unwrap();
schema.add_icon("main_icon", "notepad.exe,0").unwrap();
schema.add_icon("ext_icon", "notepad.exe,0").unwrap();
schema.add_behavior("main_behavior", "notepad.exe %1").unwrap();
schema.add_behavior("ext_behavior", "notepad.exe %1").unwrap();
schema.set_name(Some("main_name"));
schema.set_icon(Some("main_icon"));
schema.set_behavior(Some("main_behavior"));
schema.add_ext(EXT_BODY, "ext_name", "ext_icon", "ext_behavior").unwrap();
schema
}
// region: Schema
#[test]
fn test_schema() {
common::check_sandbox();
common::check_privilege();
// valid schema -> valid program
let schema = make_valid_schema();
let rv = schema.into_program();
assert!(rv.is_ok());
// missing essential parts (schema, path and etc)
let schema = Schema::new();
let rv = schema.into_program();
assert!(rv.is_err());
// invalid path
let mut schema = make_valid_schema();
schema.set_path(r"C:\");
let rv = schema.into_program();
assert!(rv.is_err());
// prepare a schema for following test
let mut schema = Schema::new();
schema.set_identifier(IDENTIFIER);
schema.set_path(APP_PATH);
// duplicate detection on add_str
let rv = schema.add_str("k", "v1");
assert!(rv.is_ok());
let rv = schema.add_str("k", "v2");
assert!(rv.is_err());
// duplicate detection on add_icon
let rv = schema.add_icon("k", "v1");
assert!(rv.is_ok());
let rv = schema.add_icon("k", "v2");
assert!(rv.is_err());
// duplicate detection on add_behavior
let rv = schema.add_behavior("k", "v1");
assert!(rv.is_ok());
let rv = schema.add_behavior("k", "v2");
assert!(rv.is_err());
// duplicate detection on add_ext
let rv = schema.add_ext(EXT_BODY, "k", "k", "k");
assert!(rv.is_ok());
let rv = schema.add_ext(EXT_BODY, "k", "k", "k");
assert!(rv.is_err());
// ext referencing non-existent map entry
schema.add_str("k2", "v").unwrap();
schema.add_icon("k2", "v").unwrap();
let rv = schema.add_ext("pacfg2", "nonexistent", "k2", "k2");
assert!(rv.is_ok());
let rv = schema.into_program();
assert!(rv.is_err());
}
// endregion
// region: Program
#[test]
fn test_program() {
common::check_sandbox();
common::check_privilege();
fn tester(scope: Scope, view: View) {
// build program
let schema = make_valid_schema();
let rv = schema.into_program();
assert!(rv.is_ok());
let mut program = rv.unwrap();
// cleanup before test
let rv = program.unregister(scope);
assert!(rv.is_ok());
// initially not registered
let rv = program.is_registered(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
// register
let rv = program.register(scope);
assert!(rv.is_ok());
// should be registered now
let rv = program.is_registered(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
// link_ext first so ext key exists before register
let rv = program.link_ext(scope, 0);
assert!(rv.is_ok());
// query_ext after link + register
let rv = program.query_ext(view, 0);
assert!(rv.is_ok());
assert!(rv.unwrap().is_some());
// unlink_ext
let rv = program.unlink_ext(scope, 0);
assert!(rv.is_ok());
// query_ext should return None
let rv = program.query_ext(view, 0);
assert!(rv.is_ok());
assert!(rv.unwrap().is_none());
// resolve_name, resolve_icon
let rv = program.resolve_name();
assert!(rv.is_ok());
let rv = program.resolve_icon();
assert!(rv.is_ok());
// exts_len, find_ext, resolve_ext
assert_eq!(program.exts_len(), 1);
assert_eq!(program.find_ext(EXT_BODY), Some(0));
let rv = program.resolve_ext(0);
assert!(rv.is_ok());
// bad index
let rv = program.resolve_ext(1);
assert!(rv.is_err());
let rv = program.link_ext(scope, 1);
assert!(rv.is_err());
let rv = program.query_ext(view, 1);
assert!(rv.is_err());
// cleanup
let rv = program.unregister(scope);
assert!(rv.is_ok());
}
tester(Scope::User, View::User);
tester(Scope::System, View::System);
}
// endregion

334
wfassoc/tests/lowlevel.rs Normal file
View File

@@ -0,0 +1,334 @@
use std::ops::Deref;
use std::str::FromStr;
use std::sync::LazyLock;
use wfassoc::lowlevel::*;
use wfassoc::win32::concept;
mod common;
static EXT: LazyLock<concept::Ext> = LazyLock::new(|| concept::Ext::from_str(".pacfg").unwrap());
static APP_FILE: LazyLock<concept::FileName> =
LazyLock::new(|| concept::FileName::from_str("passoc.exe").unwrap());
static PROG_ID: LazyLock<LosseProgId> = LazyLock::new(|| "Passoc.Pacfg".into());
static ICON: LazyLock<IconResVariant> =
LazyLock::new(|| r"%SystemRoot%\System32\imageres.dll,-72".into());
static VERB: LazyLock<ShellVerb> = LazyLock::new(|| {
let verb = concept::Verb::from_str("open").unwrap();
let cmdline = concept::CmdLine::from_str("notepad.exe %1").unwrap();
ShellVerb::new(verb, cmdline)
});
// region: AppPathsKey
#[test]
fn test_app_paths_key() {
common::check_sandbox();
common::check_privilege();
static APP_PATH: &str = r"C:\Program Files\Passoc\passoc.exe";
static APP_DIR: &str = r"C:\Program Files\Passoc";
fn tester(scope: Scope) {
let mut key = AppPathsKey::new(APP_FILE.clone());
// delete and ensure
let rv = key.delete(scope);
assert!(rv.is_ok());
let rv = key.is_exist(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.is_exist(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
// get/set default
let rv = key.set_default(scope, APP_PATH);
assert!(rv.is_ok());
let rv = key.get_default(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), APP_PATH);
// get/set path
let rv = key.set_path(scope, APP_DIR);
assert!(rv.is_ok());
let rv = key.get_path(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), APP_DIR);
// clean up
let rv = key.delete(scope);
assert!(rv.is_ok());
}
tester(Scope::User);
tester(Scope::System);
}
// endregion
// region: ApplicationsKey
#[test]
fn test_applications_key() {
common::check_sandbox();
common::check_privilege();
static FRIENDLY_APP_NAME: LazyLock<StrResVariant> =
LazyLock::new(|| "Passoc Application".into());
fn tester(scope: Scope, view: View) {
let mut key = ApplicationsKey::new(APP_FILE.clone());
// delete and ensure
let rv = key.delete(scope);
assert!(rv.is_ok());
let rv = key.is_exist(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.is_exist(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
// get/set shell verb
let rv = key.set_shell_verb(scope, Some(&VERB));
assert!(rv.is_ok());
let rv = key.get_shell_verb(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(VERB.clone()));
let rv = key.set_shell_verb(scope, None);
assert!(rv.is_ok());
let rv = key.get_shell_verb(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// get/set default icon
let rv = key.set_default_icon(scope, Some(&ICON));
assert!(rv.is_ok());
let rv = key.get_default_icon(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(ICON.clone()));
let rv = key.set_default_icon(scope, None);
assert!(rv.is_ok());
let rv = key.get_default_icon(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// get/set friendly app name
let rv = key.set_friendly_app_name(scope, Some(&FRIENDLY_APP_NAME));
assert!(rv.is_ok());
let rv = key.get_friendly_app_name(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(FRIENDLY_APP_NAME.clone()));
let rv = key.set_friendly_app_name(scope, None);
assert!(rv.is_ok());
let rv = key.get_friendly_app_name(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// get/set supported types
let rv = key.set_supported_types(scope, Some(&vec![EXT.deref()]));
assert!(rv.is_ok());
let rv = key.get_supported_types(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(vec![EXT.clone()]));
let rv = key.set_supported_types(scope, None);
assert!(rv.is_ok());
let rv = key.get_supported_types(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// get/set no open with
let rv = key.get_no_open_with(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
let rv = key.set_no_open_with(scope, true);
assert!(rv.is_ok());
let rv = key.get_no_open_with(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.set_no_open_with(scope, false);
assert!(rv.is_ok());
let rv = key.get_no_open_with(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
// clean up
let rv = key.delete(scope);
assert!(rv.is_ok());
}
tester(Scope::User, View::User);
tester(Scope::System, View::System);
}
// endregion
// region: ExtKey
#[test]
fn test_ext_key() {
common::check_sandbox();
common::check_privilege();
fn tester(scope: Scope, view: View) {
let mut key = ExtKey::new(EXT.clone());
// delete and ensure
let rv = key.delete(scope);
assert!(rv.is_ok());
let rv = key.is_exist(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.is_exist(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
// get/set default
let rv = key.set_default(scope, Some(&PROG_ID));
assert!(rv.is_ok());
let rv = key.get_default(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(PROG_ID.clone()));
let rv = key.set_default(scope, None);
assert!(rv.is_ok());
let rv = key.get_default(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// test open with progids
let rv = key.is_in_open_with_progids(view, &PROG_ID);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
let rv = key.add_into_open_with_progids(scope, &PROG_ID);
assert!(rv.is_ok());
let rv = key.is_in_open_with_progids(view, &PROG_ID);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.get_open_with_progids(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(vec![PROG_ID.clone()]));
let rv = key.remove_from_open_with_progids(scope, &PROG_ID);
assert!(rv.is_ok());
let rv = key.is_in_open_with_progids(view, &PROG_ID);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
// clean up
let rv = key.delete(scope);
assert!(rv.is_ok());
}
tester(Scope::User, View::User);
tester(Scope::System, View::System);
}
// endregion
// region: ProgIdKey
#[test]
fn test_prog_id_key() {
common::check_sandbox();
common::check_privilege();
static LEGACY_NAME: LazyLock<StrResVariant> = LazyLock::new(|| "Passoc Pacfg File".into());
static FRIENDLY_TYPE_NAME: LazyLock<StrResVariant> =
LazyLock::new(|| "Passoc Pacfg File Type".into());
fn tester(scope: Scope, view: View) {
let mut key = ProgIdKey::new(PROG_ID.clone());
// delete and ensure
let rv = key.delete(scope);
assert!(rv.is_ok());
let rv = key.is_exist(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.is_exist(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), true);
let rv = key.ensure(scope);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), false);
// get/set default (friendly name, legacy)
let rv = key.set_default(scope, Some(&LEGACY_NAME));
assert!(rv.is_ok());
let rv = key.get_default(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(LEGACY_NAME.clone()));
let rv = key.set_default(scope, None);
assert!(rv.is_ok());
let rv = key.get_default(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// get/set friendly type name
let rv = key.set_friendly_type_name(scope, Some(&FRIENDLY_TYPE_NAME));
assert!(rv.is_ok());
let rv = key.get_friendly_type_name(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(FRIENDLY_TYPE_NAME.clone()));
let rv = key.set_friendly_type_name(scope, None);
assert!(rv.is_ok());
let rv = key.get_friendly_type_name(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// get/set shell verb
let rv = key.set_shell_verb(scope, Some(&VERB));
assert!(rv.is_ok());
let rv = key.get_shell_verb(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(VERB.clone()));
let rv = key.set_shell_verb(scope, None);
assert!(rv.is_ok());
let rv = key.get_shell_verb(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// get/set default icon
let rv = key.set_default_icon(scope, Some(&ICON));
assert!(rv.is_ok());
let rv = key.get_default_icon(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), Some(ICON.clone()));
let rv = key.set_default_icon(scope, None);
assert!(rv.is_ok());
let rv = key.get_default_icon(view);
assert!(rv.is_ok());
assert_eq!(rv.unwrap(), None);
// clean up
let rv = key.delete(scope);
assert!(rv.is_ok());
}
tester(Scope::User, View::User);
tester(Scope::System, View::System);
}
// endregion