mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-02 17:39:58 +08:00
Implement file association Open a file from Finder/Explorer (#2918)
* implement MacOS openFile/openFiles events * wip: windows file association * fix macro import * add file icon copy * try copy icon * keep only required part of scripts * update config schema * fix json * set fileAssociation for mac via config * proper iconName handling * add fileAssociation icon generator * fix file association icons bundle * don't break compatibility * remove mimeType as not supported linux for now * add documentation * adjust config schema * restore formatting * remove unused option in file association * get rid of openFiles mac os. change configuration structure * remove unused channel * fix documentation * fix typo --------- Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
parent
42708e7f40
commit
6c46f6b41c
@ -11,7 +11,7 @@
|
|||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "WailsContext.h"
|
#import "WailsContext.h"
|
||||||
|
|
||||||
@interface AppDelegate : NSResponder <NSTouchBarProvider>
|
@interface AppDelegate : NSResponder <NSApplicationDelegate, NSTouchBarProvider>
|
||||||
|
|
||||||
@property bool alwaysOnTop;
|
@property bool alwaysOnTop;
|
||||||
@property bool startHidden;
|
@property bool startHidden;
|
||||||
@ -20,4 +20,6 @@
|
|||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
extern void HandleOpenFile(char *);
|
||||||
|
|
||||||
#endif /* AppDelegate_h */
|
#endif /* AppDelegate_h */
|
||||||
|
@ -11,9 +11,17 @@
|
|||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
|
||||||
return NO;
|
{
|
||||||
|
const char* utf8FileName = filename.UTF8String;
|
||||||
|
HandleOpenFile((char*)utf8FileName);
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
|
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
if (self.alwaysOnTop) {
|
if (self.alwaysOnTop) {
|
||||||
|
@ -38,6 +38,7 @@ const startURL = "wails://wails/"
|
|||||||
var messageBuffer = make(chan string, 100)
|
var messageBuffer = make(chan string, 100)
|
||||||
var requestBuffer = make(chan webview.Request, 100)
|
var requestBuffer = make(chan webview.Request, 100)
|
||||||
var callbackBuffer = make(chan uint, 10)
|
var callbackBuffer = make(chan uint, 10)
|
||||||
|
var openFilepathBuffer = make(chan string, 100)
|
||||||
|
|
||||||
type Frontend struct {
|
type Frontend struct {
|
||||||
|
|
||||||
@ -107,15 +108,23 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.
|
|||||||
|
|
||||||
go result.startMessageProcessor()
|
go result.startMessageProcessor()
|
||||||
go result.startCallbackProcessor()
|
go result.startCallbackProcessor()
|
||||||
|
go result.startFileOpenProcessor()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Frontend) startFileOpenProcessor() {
|
||||||
|
for filePath := range openFilepathBuffer {
|
||||||
|
f.ProcessOpenFileEvent(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Frontend) startMessageProcessor() {
|
func (f *Frontend) startMessageProcessor() {
|
||||||
for message := range messageBuffer {
|
for message := range messageBuffer {
|
||||||
f.processMessage(message)
|
f.processMessage(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Frontend) startRequestProcessor() {
|
func (f *Frontend) startRequestProcessor() {
|
||||||
for request := range requestBuffer {
|
for request := range requestBuffer {
|
||||||
f.assets.ServeWebViewRequest(request)
|
f.assets.ServeWebViewRequest(request)
|
||||||
@ -355,6 +364,12 @@ func (f *Frontend) processMessage(message string) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Frontend) ProcessOpenFileEvent(filePath string) {
|
||||||
|
if f.frontendOptions.Mac != nil && f.frontendOptions.Mac.OnFileOpen != nil {
|
||||||
|
f.frontendOptions.Mac.OnFileOpen(filePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (f *Frontend) Callback(message string) {
|
func (f *Frontend) Callback(message string) {
|
||||||
escaped, err := json.Marshal(message)
|
escaped, err := json.Marshal(message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -398,3 +413,9 @@ func processCallback(callbackID uint) {
|
|||||||
func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) {
|
func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) {
|
||||||
requestBuffer <- webview.NewRequest(wkURLSchemeTask)
|
requestBuffer <- webview.NewRequest(wkURLSchemeTask)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//export HandleOpenFile
|
||||||
|
func HandleOpenFile(filePath *C.char) {
|
||||||
|
goFilepath := C.GoString(filePath)
|
||||||
|
openFilepathBuffer <- goFilepath
|
||||||
|
}
|
||||||
|
@ -216,11 +216,20 @@ type Author struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
CompanyName string `json:"companyName"`
|
CompanyName string `json:"companyName"`
|
||||||
ProductName string `json:"productName"`
|
ProductName string `json:"productName"`
|
||||||
ProductVersion string `json:"productVersion"`
|
ProductVersion string `json:"productVersion"`
|
||||||
Copyright *string `json:"copyright"`
|
Copyright *string `json:"copyright"`
|
||||||
Comments *string `json:"comments"`
|
Comments *string `json:"comments"`
|
||||||
|
FileAssociations []FileAssociation `json:"fileAssociations"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileAssociation struct {
|
||||||
|
Ext string `json:"ext"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IconName string `json:"iconName"`
|
||||||
|
Role string `json:"role"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Bindings struct {
|
type Bindings struct {
|
||||||
|
@ -23,10 +23,29 @@
|
|||||||
<string>true</string>
|
<string>true</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>{{.Info.Copyright}}</string>
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
{{if .Info.FileAssociations}}
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Ext}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>{{.IconName}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsLocalNetworking</key>
|
<key>NSAllowsLocalNetworking</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -23,5 +23,24 @@
|
|||||||
<string>true</string>
|
<string>true</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>{{.Info.Copyright}}</string>
|
<string>{{.Info.Copyright}}</string>
|
||||||
|
{{if .Info.FileAssociations}}
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>{{.Ext}}</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>{{.Role}}</string>
|
||||||
|
<key>CFBundleTypeIconFile</key>
|
||||||
|
<string>{{.IconName}}</string>
|
||||||
|
</dict>
|
||||||
|
{{end}}
|
||||||
|
</array>
|
||||||
|
{{end}}
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -3,10 +3,10 @@ Unicode true
|
|||||||
####
|
####
|
||||||
## Please note: Template replacements don't work in this file. They are provided with default defines like
|
## Please note: Template replacements don't work in this file. They are provided with default defines like
|
||||||
## mentioned underneath.
|
## mentioned underneath.
|
||||||
## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
|
## If the keyword is not defined, "wails_tools.nsh" will populate them with the values from ProjectInfo.
|
||||||
## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
|
## If they are defined here, "wails_tools.nsh" will not touch them. This allows to use this project.nsi manually
|
||||||
## from outside of Wails for debugging and development of the installer.
|
## from outside of Wails for debugging and development of the installer.
|
||||||
##
|
##
|
||||||
## For development first make a wails nsis build to populate the "wails_tools.nsh":
|
## For development first make a wails nsis build to populate the "wails_tools.nsh":
|
||||||
## > wails build --target windows/amd64 --nsis
|
## > wails build --target windows/amd64 --nsis
|
||||||
## Then you can call makensis on this file with specifying the path to your binary:
|
## Then you can call makensis on this file with specifying the path to your binary:
|
||||||
@ -17,7 +17,7 @@ Unicode true
|
|||||||
## For a installer with both architectures:
|
## For a installer with both architectures:
|
||||||
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
|
## > makensis -DARG_WAILS_AMD64_BINARY=..\..\bin\app-amd64.exe -DARG_WAILS_ARM64_BINARY=..\..\bin\app-arm64.exe
|
||||||
####
|
####
|
||||||
## The following information is taken from the ProjectInfo file, but they can be overwritten here.
|
## The following information is taken from the ProjectInfo file, but they can be overwritten here.
|
||||||
####
|
####
|
||||||
## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
|
## !define INFO_PROJECTNAME "MyProject" # Default "{{.Name}}"
|
||||||
## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
|
## !define INFO_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
|
||||||
@ -85,16 +85,18 @@ Section
|
|||||||
!insertmacro wails.webview2runtime
|
!insertmacro wails.webview2runtime
|
||||||
|
|
||||||
SetOutPath $INSTDIR
|
SetOutPath $INSTDIR
|
||||||
|
|
||||||
!insertmacro wails.files
|
!insertmacro wails.files
|
||||||
|
|
||||||
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||||
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
|
||||||
|
|
||||||
|
!insertmacro wails.associateFiles
|
||||||
|
|
||||||
!insertmacro wails.writeUninstaller
|
!insertmacro wails.writeUninstaller
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
|
||||||
Section "uninstall"
|
Section "uninstall"
|
||||||
!insertmacro wails.setShellContext
|
!insertmacro wails.setShellContext
|
||||||
|
|
||||||
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
|
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
|
||||||
@ -104,5 +106,7 @@ Section "uninstall"
|
|||||||
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
|
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
|
||||||
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
|
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
|
||||||
|
|
||||||
|
!insertmacro wails.unassociateFiles
|
||||||
|
|
||||||
!insertmacro wails.deleteUninstaller
|
!insertmacro wails.deleteUninstaller
|
||||||
SectionEnd
|
SectionEnd
|
||||||
|
@ -163,17 +163,58 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
|
|||||||
Goto ok
|
Goto ok
|
||||||
${EndIf}
|
${EndIf}
|
||||||
${EndIf}
|
${EndIf}
|
||||||
|
|
||||||
SetDetailsPrint both
|
SetDetailsPrint both
|
||||||
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
|
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
|
||||||
SetDetailsPrint listonly
|
SetDetailsPrint listonly
|
||||||
|
|
||||||
InitPluginsDir
|
InitPluginsDir
|
||||||
CreateDirectory "$pluginsdir\webview2bootstrapper"
|
CreateDirectory "$pluginsdir\webview2bootstrapper"
|
||||||
SetOutPath "$pluginsdir\webview2bootstrapper"
|
SetOutPath "$pluginsdir\webview2bootstrapper"
|
||||||
File "tmp\MicrosoftEdgeWebview2Setup.exe"
|
File "tmp\MicrosoftEdgeWebview2Setup.exe"
|
||||||
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
|
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'
|
||||||
|
|
||||||
SetDetailsPrint both
|
SetDetailsPrint both
|
||||||
ok:
|
ok:
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
|
# Copy of APP_ASSOCIATE and APP_UNASSOCIATE macros from here https://gist.github.com/nikku/281d0ef126dbc215dd58bfd5b3a5cd5b
|
||||||
|
!macro APP_ASSOCIATE EXT FILECLASS DESCRIPTION ICON COMMANDTEXT COMMAND
|
||||||
|
; Backup the previously associated file class
|
||||||
|
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" ""
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "${FILECLASS}_backup" "$R0"
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "${FILECLASS}"
|
||||||
|
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}" "" `${DESCRIPTION}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\DefaultIcon" "" `${ICON}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell" "" "open"
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open" "" `${COMMANDTEXT}`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\${FILECLASS}\shell\open\command" "" `${COMMAND}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro APP_UNASSOCIATE EXT FILECLASS
|
||||||
|
; Backup the previously associated file class
|
||||||
|
ReadRegStr $R0 SHELL_CONTEXT "Software\Classes\.${EXT}" `${FILECLASS}_backup`
|
||||||
|
WriteRegStr SHELL_CONTEXT "Software\Classes\.${EXT}" "" "$R0"
|
||||||
|
|
||||||
|
DeleteRegKey SHELL_CONTEXT `Software\Classes\${FILECLASS}`
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.associateFiles
|
||||||
|
; Create file associations
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
!insertmacro APP_ASSOCIATE "{{.Ext}}" "{{.Name}}" "{{.Description}}" "$INSTDIR\{{.IconName}}.ico" "Open with ${INFO_PRODUCTNAME}" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\""
|
||||||
|
|
||||||
|
File "..\{{.IconName}}.ico"
|
||||||
|
{{end}}
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro wails.unassociateFiles
|
||||||
|
; Delete app associations
|
||||||
|
{{range .Info.FileAssociations}}
|
||||||
|
!insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}"
|
||||||
|
|
||||||
|
Delete "$INSTDIR\{{.IconName}}.ico"
|
||||||
|
{{end}}
|
||||||
|
!macroend
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/leaanthony/winicon"
|
"github.com/leaanthony/winicon"
|
||||||
"github.com/tc-hib/winres"
|
"github.com/tc-hib/winres"
|
||||||
"github.com/tc-hib/winres/version"
|
"github.com/tc-hib/winres/version"
|
||||||
|
"github.com/wailsapp/wails/v2/internal/project"
|
||||||
"image"
|
"image"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -95,12 +96,20 @@ func packageApplicationForDarwin(options *Options) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate Icons
|
// Generate App Icon
|
||||||
err = processApplicationIcon(options, resourceDir)
|
err = processDarwinIcon(options.ProjectData, "appicon", resourceDir, "iconfile")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate FileAssociation Icons
|
||||||
|
for _, fileAssociation := range options.ProjectData.Info.FileAssociations {
|
||||||
|
err = processDarwinIcon(options.ProjectData, fileAssociation.IconName, resourceDir, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
options.CompiledBinary = packedBinaryPath
|
options.CompiledBinary = packedBinaryPath
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -124,8 +133,8 @@ func processPList(options *Options, contentsDirectory string) error {
|
|||||||
return os.WriteFile(targetFile, content, 0644)
|
return os.WriteFile(targetFile, content, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processApplicationIcon(options *Options, resourceDir string) (err error) {
|
func processDarwinIcon(projectData *project.Project, iconName string, resourceDir string, destIconName string) (err error) {
|
||||||
appIcon, err := buildassets.ReadFile(options.ProjectData, "appicon.png")
|
appIcon, err := buildassets.ReadFile(projectData, iconName+".png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -135,7 +144,11 @@ func processApplicationIcon(options *Options, resourceDir string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tgtBundle := filepath.Join(resourceDir, "iconfile.icns")
|
if destIconName == "" {
|
||||||
|
destIconName = iconName
|
||||||
|
}
|
||||||
|
|
||||||
|
tgtBundle := filepath.Join(resourceDir, destIconName+".icns")
|
||||||
dest, err := os.Create(tgtBundle)
|
dest, err := os.Create(tgtBundle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -151,13 +164,21 @@ func processApplicationIcon(options *Options, resourceDir string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func packageApplicationForWindows(options *Options) error {
|
func packageApplicationForWindows(options *Options) error {
|
||||||
// Generate icon
|
// Generate app icon
|
||||||
var err error
|
var err error
|
||||||
err = generateIcoFile(options)
|
err = generateIcoFile(options, "appicon", "icon")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate FileAssociation Icons
|
||||||
|
for _, fileAssociation := range options.ProjectData.Info.FileAssociations {
|
||||||
|
err = generateIcoFile(options, fileAssociation.IconName, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create syso file
|
// Create syso file
|
||||||
err = compileResources(options)
|
err = compileResources(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -171,13 +192,18 @@ func packageApplicationForLinux(_ *Options) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateIcoFile(options *Options) error {
|
func generateIcoFile(options *Options, iconName string, destIconName string) error {
|
||||||
content, err := buildassets.ReadFile(options.ProjectData, "appicon.png")
|
content, err := buildassets.ReadFile(options.ProjectData, iconName+".png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if destIconName == "" {
|
||||||
|
destIconName = iconName
|
||||||
|
}
|
||||||
|
|
||||||
// Check ico file exists already
|
// Check ico file exists already
|
||||||
icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/icon.ico")
|
icoFile := buildassets.GetLocalPath(options.ProjectData, "windows/"+destIconName+".ico")
|
||||||
if !fs.FileExists(icoFile) {
|
if !fs.FileExists(icoFile) {
|
||||||
if dir := filepath.Dir(icoFile); !fs.DirExists(dir) {
|
if dir := filepath.Dir(icoFile); !fs.DirExists(dir) {
|
||||||
if err := fs.MkDirs(dir, 0755); err != nil {
|
if err := fs.MkDirs(dir, 0755); err != nil {
|
||||||
|
@ -22,6 +22,7 @@ type Options struct {
|
|||||||
WindowIsTranslucent bool
|
WindowIsTranslucent bool
|
||||||
Preferences *Preferences
|
Preferences *Preferences
|
||||||
//ActivationPolicy ActivationPolicy
|
//ActivationPolicy ActivationPolicy
|
||||||
About *AboutInfo
|
About *AboutInfo
|
||||||
|
OnFileOpen func(filePath string) `json:"-"`
|
||||||
//URLHandlers map[string]func(string)
|
//URLHandlers map[string]func(string)
|
||||||
}
|
}
|
||||||
|
184
website/docs/guides/file-association.mdx
Normal file
184
website/docs/guides/file-association.mdx
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# File Association
|
||||||
|
|
||||||
|
File association feature allows you to associate specific file types with your app so that when users open those files,
|
||||||
|
your app is launched to handle them. This can be particularly useful for text editors, image viewers, or any application
|
||||||
|
that works with specific file formats. In this guide, we'll walk through the steps to implement file association in Wails app.
|
||||||
|
|
||||||
|
|
||||||
|
## Set Up File Association:
|
||||||
|
To set up file association, you need to modify your application's wails.json file.
|
||||||
|
In "info" section add a "fileAssociations" section specifying the file types your app should be associated with.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"fileAssociations": [
|
||||||
|
{
|
||||||
|
"ext": "wails",
|
||||||
|
"name": "Wails",
|
||||||
|
"description": "Wails Application File",
|
||||||
|
"iconName": "wailsFileIcon",
|
||||||
|
"role": "Editor"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "jpg",
|
||||||
|
"name": "JPEG",
|
||||||
|
"description": "Image File",
|
||||||
|
"iconName": "jpegFileIcon",
|
||||||
|
"role": "Editor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Property | Description |
|
||||||
|
|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| ext | The extension (minus the leading period). e.g. png |
|
||||||
|
| name | The name. e.g. PNG File |
|
||||||
|
| iconName | The icon name without extension. Icons should be located in build folder. Proper icons will be generated from .png file for both macOS and Windows |
|
||||||
|
| description | Windows-only. The description. It is displayed on the `Type` column on Windows Explorer. |
|
||||||
|
| role | macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole. |
|
||||||
|
|
||||||
|
## Platform Specifics:
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
When you open file (or files) with your app, the system will launch your app and call the `OnFileOpen` function in your Wails app. Example:
|
||||||
|
```go title="main.go"
|
||||||
|
func main() {
|
||||||
|
// Create application with options
|
||||||
|
err := wails.Run(&options.App{
|
||||||
|
Title: "wails-open-file",
|
||||||
|
Width: 1024,
|
||||||
|
Height: 768,
|
||||||
|
AssetServer: &assetserver.Options{
|
||||||
|
Assets: assets,
|
||||||
|
},
|
||||||
|
BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1},
|
||||||
|
Mac: &mac.Options{
|
||||||
|
OnFileOpen: func(filePaths []string) { println(filestring) },
|
||||||
|
},
|
||||||
|
Bind: []interface{}{
|
||||||
|
app,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
println("Error:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
On Windows file association is supported only with NSIS installer. During installation, the installer will create a
|
||||||
|
registry entry for your file associations. When you open file with your app, new instance of app is launched and file path is passed
|
||||||
|
as argument to your app. To handle this you should parse command line arguments in your app. Example:
|
||||||
|
```go title="main.go"
|
||||||
|
func main() {
|
||||||
|
argsWithoutProg := os.Args[1:]
|
||||||
|
|
||||||
|
if len(argsWithoutProg) != 0 {
|
||||||
|
println("launchArgs", argsWithoutProg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
Currently, Wails doesn't support bundling for Linux. So, you need to create file associations manually.
|
||||||
|
For example if you distribute your app as a .deb package, you can create file associations by adding required files in you bundle.
|
||||||
|
You can use [nfpm](https://nfpm.goreleaser.com/) to create .deb package for your app.
|
||||||
|
|
||||||
|
1. Create a .desktop file for your app and specify file associations there. Example:
|
||||||
|
```ini
|
||||||
|
[Desktop Entry]
|
||||||
|
Categories=Office
|
||||||
|
Exec=/usr/bin/wails-open-file %u
|
||||||
|
Icon=wails-open-file.png
|
||||||
|
Name=wails-open-file
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
MimeType=application/x-wails;application/x-test
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Create mime types file. Example:
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
|
<mime-type type="application/x-wails">
|
||||||
|
<comment>Wails Application File</comment>
|
||||||
|
<glob pattern="*.wails"/>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create icons for your file types. SVG icons are recommended.
|
||||||
|
4. Prepare postInstall/postRemove scripts for your package. Example:
|
||||||
|
```sh
|
||||||
|
# reload mime types to register file associations
|
||||||
|
update-mime-database /usr/share/mime
|
||||||
|
# reload desktop database to load app in list of available
|
||||||
|
update-desktop-database /usr/share/applications
|
||||||
|
# update icons
|
||||||
|
update-icon-caches /usr/share/icons/*
|
||||||
|
```
|
||||||
|
5. Configure nfpm to use your scripts and files. Example:
|
||||||
|
```yaml
|
||||||
|
name: "wails-open-file"
|
||||||
|
arch: "arm64"
|
||||||
|
platform: "linux"
|
||||||
|
version: "1.0.0"
|
||||||
|
section: "default"
|
||||||
|
priority: "extra"
|
||||||
|
maintainer: "FooBarCorp <FooBarCorp@gmail.com>"
|
||||||
|
description: "Sample Package"
|
||||||
|
vendor: "FooBarCorp"
|
||||||
|
homepage: "http://example.com"
|
||||||
|
license: "MIT"
|
||||||
|
contents:
|
||||||
|
- src: ../bin/wails-open-file
|
||||||
|
dst: /usr/bin/wails-open-file
|
||||||
|
- src: ./main.desktop
|
||||||
|
dst: /usr/share/applications/wails-open-file.desktop
|
||||||
|
- src: ./application-wails-mime.xml
|
||||||
|
dst: /usr/share/mime/packages/application-x-wails.xml
|
||||||
|
- src: ./application-test-mime.xml
|
||||||
|
dst: /usr/share/mime/packages/application-x-test.xml
|
||||||
|
- src: ../appicon.svg
|
||||||
|
dst: /usr/share/icons/hicolor/scalable/apps/wails-open-file.svg
|
||||||
|
- src: ../wailsFileIcon.svg
|
||||||
|
dst: /usr/share/icons/hicolor/scalable/mimetypes/application-x-wails.svg
|
||||||
|
- src: ../testFileIcon.svg
|
||||||
|
dst: /usr/share/icons/hicolor/scalable/mimetypes/application-x-test.svg
|
||||||
|
# copy icons to Yaru theme as well. For some reason Ubuntu didn't pick up fileicons from hicolor theme
|
||||||
|
- src: ../appicon.svg
|
||||||
|
dst: /usr/share/icons/Yaru/scalable/apps/wails-open-file.svg
|
||||||
|
- src: ../wailsFileIcon.svg
|
||||||
|
dst: /usr/share/icons/Yaru/scalable/mimetypes/application-x-wails.svg
|
||||||
|
- src: ../testFileIcon.svg
|
||||||
|
dst: /usr/share/icons/Yaru/scalable/mimetypes/application-x-test.svg
|
||||||
|
scripts:
|
||||||
|
postinstall: ./postInstall.sh
|
||||||
|
postremove: ./postRemove.sh
|
||||||
|
```
|
||||||
|
6. Build your .deb package using nfpm:
|
||||||
|
```sh
|
||||||
|
nfpm pkg --packager deb --target .
|
||||||
|
```
|
||||||
|
7. Now when your package is installed, your app will be associated with specified file types. When you open file with your app,
|
||||||
|
new instance of app is launched and file path is passed as argument to your app.
|
||||||
|
To handle this you should parse command line arguments in your app. Example:
|
||||||
|
```go title="main.go"
|
||||||
|
func main() {
|
||||||
|
argsWithoutProg := os.Args[1:]
|
||||||
|
|
||||||
|
if len(argsWithoutProg) != 0 {
|
||||||
|
println("launchArgs", argsWithoutProg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations:
|
||||||
|
On Windows and Linux when associated file is opened, new instance of your app is launched.
|
||||||
|
Currently, Wails doesn't support opening files in already running app. There is plugin for single instance support for v3 in development.
|
@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Added support for enabling/disabling swipe gestures for Windows WebView2. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2878)
|
- Added support for enabling/disabling swipe gestures for Windows WebView2. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2878)
|
||||||
- When building with `-devtools` flag, CMD/CTRL+SHIFT+F12 can be used to open the devtools. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2915)
|
- When building with `-devtools` flag, CMD/CTRL+SHIFT+F12 can be used to open the devtools. Added by @leaanthony in [PR](https://github.com/wailsapp/wails/pull/2915)
|
||||||
|
– Added file association support for macOS and Windows. Added by @APshenkin in [PR](https://github.com/wailsapp/wails/pull/2918)
|
||||||
- Added support for setting some of the Webview preferences, `textInteractionEnabled` and `tabFocusesLinks` on Mac. Added by @fkhadra in [PR](https://github.com/wailsapp/wails/pull/2937)
|
- Added support for setting some of the Webview preferences, `textInteractionEnabled` and `tabFocusesLinks` on Mac. Added by @fkhadra in [PR](https://github.com/wailsapp/wails/pull/2937)
|
||||||
- Added support for enabling/disabling fullscreen of the Webview on Mac. Added by @fkhadra in [PR](https://github.com/wailsapp/wails/pull/2953)
|
- Added support for enabling/disabling fullscreen of the Webview on Mac. Added by @fkhadra in [PR](https://github.com/wailsapp/wails/pull/2953)
|
||||||
- Added French README.fr.md page. Added by @nejos97 in [PR](https://github.com/wailsapp/wails/pull/2943)
|
- Added French README.fr.md page. Added by @nejos97 in [PR](https://github.com/wailsapp/wails/pull/2943)
|
||||||
|
@ -72,10 +72,17 @@
|
|||||||
"frontend:dev:serverUrl": {
|
"frontend:dev:serverUrl": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "URL to a 3rd party dev server to be used to serve assets (eg. Vite). If this is set to 'auto', then the devServerUrl will be inferred from the Vite output",
|
"description": "URL to a 3rd party dev server to be used to serve assets (eg. Vite). If this is set to 'auto', then the devServerUrl will be inferred from the Vite output",
|
||||||
"examples": [ "auto", "http://localhost:3000" ],
|
"examples": [
|
||||||
|
"auto",
|
||||||
|
"http://localhost:3000"
|
||||||
|
],
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{ "format": "uri" },
|
{
|
||||||
{ "const": "auto" }
|
"format": "uri"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"const": "auto"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"wailsjsdir": {
|
"wailsjsdir": {
|
||||||
@ -87,7 +94,9 @@
|
|||||||
"version": {
|
"version": {
|
||||||
"description": "Project config version",
|
"description": "Project config version",
|
||||||
"default": "2",
|
"default": "2",
|
||||||
"enum": [ "2" ]
|
"enum": [
|
||||||
|
"2"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"outputfilename": {
|
"outputfilename": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -123,7 +132,9 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "The application author",
|
"description": "The application author",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string" },
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "email"
|
"format": "email"
|
||||||
@ -156,6 +167,39 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A short comment for the app",
|
"description": "A short comment for the app",
|
||||||
"default": "Built using Wails (https://wails.io)"
|
"default": "Built using Wails (https://wails.io)"
|
||||||
|
},
|
||||||
|
"fileAssociations": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "File associations for the app",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"ext": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The extension (minus the leading period). e.g. png"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name. e.g. PNG File"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Windows-only. The description. It is displayed on the `Type` column on Windows Explorer."
|
||||||
|
},
|
||||||
|
"iconName": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The icon name without extension. Icons should be located in build folder. Proper icons will be generated from .png file for both macOS and Windows)"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"description": "macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole.",
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/BundleTypeRole"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -185,7 +229,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"garbleargs": ["obfuscated"]
|
"garbleargs": [
|
||||||
|
"obfuscated"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"OsHook": {
|
"OsHook": {
|
||||||
@ -203,11 +249,21 @@
|
|||||||
"description": "Build hooks for different targets.",
|
"description": "Build hooks for different targets.",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"{GOOS}/{GOARCH}": { "$ref": "#/definitions/OsArchHook" },
|
"{GOOS}/{GOARCH}": {
|
||||||
"{GOOS}/*": { "$ref": "#/definitions/OsHook" },
|
"$ref": "#/definitions/OsArchHook"
|
||||||
"windows/*": { "$ref": "#/definitions/OsHook" },
|
},
|
||||||
"linux/*": { "$ref": "#/definitions/OsHook" },
|
"{GOOS}/*": {
|
||||||
"darwin/*": { "$ref": "#/definitions/OsHook" },
|
"$ref": "#/definitions/OsHook"
|
||||||
|
},
|
||||||
|
"windows/*": {
|
||||||
|
"$ref": "#/definitions/OsHook"
|
||||||
|
},
|
||||||
|
"linux/*": {
|
||||||
|
"$ref": "#/definitions/OsHook"
|
||||||
|
},
|
||||||
|
"darwin/*": {
|
||||||
|
"$ref": "#/definitions/OsHook"
|
||||||
|
},
|
||||||
"*/*": {
|
"*/*": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Executed at build level before/after a build"
|
"description": "Executed at build level before/after a build"
|
||||||
@ -225,26 +281,66 @@
|
|||||||
"description": "Executed at build level before/after a build of the specific platform"
|
"description": "Executed at build level before/after a build of the specific platform"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"BundleTypeRole": {
|
||||||
|
"description": "macOS-only. Corresponds to CFBundleTypeRole",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"description": "CFBundleTypeRole.Editor. Files can be read and edited.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Editor"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "CFBundleTypeRole.Viewer. Files can be read.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Viewer"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "CFBundleTypeRole.Shell",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"Shell"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "CFBundleTypeRole.QLGenerator",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"QLGenerator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "CFBundleTypeRole.None",
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"None"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "Bindings configurations",
|
"description": "Bindings configurations",
|
||||||
"properties": {
|
"properties": {
|
||||||
"ts_generation": {
|
"ts_generation": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "model.ts file generation config",
|
"description": "model.ts file generation config",
|
||||||
"properties": {
|
"properties": {
|
||||||
"prefix": {
|
"prefix": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "All generated JavaScript entities will be prefixed with this value"
|
"description": "All generated JavaScript entities will be prefixed with this value"
|
||||||
},
|
},
|
||||||
"suffix": {
|
"suffix": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "All generated JavaScript entities will be suffixed with this value"
|
"description": "All generated JavaScript entities will be suffixed with this value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user