Skip to content

Commit

Permalink
Implement file association Open a file from Finder/Explorer (#2918)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
APshenkin and leaanthony authored Oct 21, 2023
1 parent 42708e7 commit 6c46f6b
Show file tree
Hide file tree
Showing 13 changed files with 492 additions and 61 deletions.
4 changes: 3 additions & 1 deletion v2/internal/frontend/desktop/darwin/AppDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#import <Cocoa/Cocoa.h>
#import "WailsContext.h"

@interface AppDelegate : NSResponder <NSTouchBarProvider>
@interface AppDelegate : NSResponder <NSApplicationDelegate, NSTouchBarProvider>

@property bool alwaysOnTop;
@property bool startHidden;
Expand All @@ -20,4 +20,6 @@

@end

extern void HandleOpenFile(char *);

#endif /* AppDelegate_h */
10 changes: 9 additions & 1 deletion v2/internal/frontend/desktop/darwin/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@
#import "AppDelegate.h"

@implementation AppDelegate
-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
{
const char* utf8FileName = filename.UTF8String;
HandleOpenFile((char*)utf8FileName);
return YES;
}

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return NO;
return NO;
}

- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
if (self.alwaysOnTop) {
Expand Down
21 changes: 21 additions & 0 deletions v2/internal/frontend/desktop/darwin/frontend.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const startURL = "wails://wails/"
var messageBuffer = make(chan string, 100)
var requestBuffer = make(chan webview.Request, 100)
var callbackBuffer = make(chan uint, 10)
var openFilepathBuffer = make(chan string, 100)

type Frontend struct {

Expand Down Expand Up @@ -107,15 +108,23 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.

go result.startMessageProcessor()
go result.startCallbackProcessor()
go result.startFileOpenProcessor()

return result
}

func (f *Frontend) startFileOpenProcessor() {
for filePath := range openFilepathBuffer {
f.ProcessOpenFileEvent(filePath)
}
}

func (f *Frontend) startMessageProcessor() {
for message := range messageBuffer {
f.processMessage(message)
}
}

func (f *Frontend) startRequestProcessor() {
for request := range requestBuffer {
f.assets.ServeWebViewRequest(request)
Expand Down Expand Up @@ -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) {
escaped, err := json.Marshal(message)
if err != nil {
Expand Down Expand Up @@ -398,3 +413,9 @@ func processCallback(callbackID uint) {
func processURLRequest(_ unsafe.Pointer, wkURLSchemeTask unsafe.Pointer) {
requestBuffer <- webview.NewRequest(wkURLSchemeTask)
}

//export HandleOpenFile
func HandleOpenFile(filePath *C.char) {
goFilepath := C.GoString(filePath)
openFilepathBuffer <- goFilepath
}
19 changes: 14 additions & 5 deletions v2/internal/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,20 @@ type Author struct {
}

type Info struct {
CompanyName string `json:"companyName"`
ProductName string `json:"productName"`
ProductVersion string `json:"productVersion"`
Copyright *string `json:"copyright"`
Comments *string `json:"comments"`
CompanyName string `json:"companyName"`
ProductName string `json:"productName"`
ProductVersion string `json:"productVersion"`
Copyright *string `json:"copyright"`
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 {
Expand Down
21 changes: 20 additions & 1 deletion v2/pkg/buildassets/build/darwin/Info.dev.plist
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,29 @@
<string>true</string>
<key>NSHumanReadableCopyright</key>
<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>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>
</plist>
21 changes: 20 additions & 1 deletion v2/pkg/buildassets/build/darwin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,24 @@
<string>true</string>
<key>NSHumanReadableCopyright</key>
<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>
</plist>
</plist>
16 changes: 10 additions & 6 deletions v2/pkg/buildassets/build/windows/installer/project.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ Unicode true
####
## Please note: Template replacements don't work in this file. They are provided with default defines like
## mentioned underneath.
## 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 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
## 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":
## > wails build --target windows/amd64 --nsis
## Then you can call makensis on this file with specifying the path to your binary:
Expand All @@ -17,7 +17,7 @@ Unicode true
## For a installer with both architectures:
## > 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_COMPANYNAME "MyCompany" # Default "{{.Info.CompanyName}}"
Expand Down Expand Up @@ -85,16 +85,18 @@ Section
!insertmacro wails.webview2runtime

SetOutPath $INSTDIR

!insertmacro wails.files

CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"

!insertmacro wails.associateFiles

!insertmacro wails.writeUninstaller
SectionEnd

Section "uninstall"
Section "uninstall"
!insertmacro wails.setShellContext

RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
Expand All @@ -104,5 +106,7 @@ Section "uninstall"
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"

!insertmacro wails.unassociateFiles

!insertmacro wails.deleteUninstaller
SectionEnd
49 changes: 45 additions & 4 deletions v2/pkg/buildassets/build/windows/installer/wails_tools.nsh
Original file line number Diff line number Diff line change
Expand Up @@ -163,17 +163,58 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}"
Goto ok
${EndIf}
${EndIf}

SetDetailsPrint both
DetailPrint "${WAILS_INSTALL_WEBVIEW_DETAILPRINT}"
SetDetailsPrint listonly

InitPluginsDir
CreateDirectory "$pluginsdir\webview2bootstrapper"
SetOutPath "$pluginsdir\webview2bootstrapper"
File "tmp\MicrosoftEdgeWebview2Setup.exe"
ExecWait '"$pluginsdir\webview2bootstrapper\MicrosoftEdgeWebview2Setup.exe" /silent /install'

SetDetailsPrint both
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
46 changes: 36 additions & 10 deletions v2/pkg/commands/build/packager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/leaanthony/winicon"
"github.com/tc-hib/winres"
"github.com/tc-hib/winres/version"
"github.com/wailsapp/wails/v2/internal/project"
"image"
"os"
"path/filepath"
Expand Down Expand Up @@ -95,12 +96,20 @@ func packageApplicationForDarwin(options *Options) error {
return err
}

// Generate Icons
err = processApplicationIcon(options, resourceDir)
// Generate App Icon
err = processDarwinIcon(options.ProjectData, "appicon", resourceDir, "iconfile")
if err != nil {
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

return nil
Expand All @@ -124,8 +133,8 @@ func processPList(options *Options, contentsDirectory string) error {
return os.WriteFile(targetFile, content, 0644)
}

func processApplicationIcon(options *Options, resourceDir string) (err error) {
appIcon, err := buildassets.ReadFile(options.ProjectData, "appicon.png")
func processDarwinIcon(projectData *project.Project, iconName string, resourceDir string, destIconName string) (err error) {
appIcon, err := buildassets.ReadFile(projectData, iconName+".png")
if err != nil {
return err
}
Expand All @@ -135,7 +144,11 @@ func processApplicationIcon(options *Options, resourceDir string) (err error) {
return err
}

tgtBundle := filepath.Join(resourceDir, "iconfile.icns")
if destIconName == "" {
destIconName = iconName
}

tgtBundle := filepath.Join(resourceDir, destIconName+".icns")
dest, err := os.Create(tgtBundle)
if err != nil {
return err
Expand All @@ -151,13 +164,21 @@ func processApplicationIcon(options *Options, resourceDir string) (err error) {
}

func packageApplicationForWindows(options *Options) error {
// Generate icon
// Generate app icon
var err error
err = generateIcoFile(options)
err = generateIcoFile(options, "appicon", "icon")
if err != nil {
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
err = compileResources(options)
if err != nil {
Expand All @@ -171,13 +192,18 @@ func packageApplicationForLinux(_ *Options) error {
return nil
}

func generateIcoFile(options *Options) error {
content, err := buildassets.ReadFile(options.ProjectData, "appicon.png")
func generateIcoFile(options *Options, iconName string, destIconName string) error {
content, err := buildassets.ReadFile(options.ProjectData, iconName+".png")
if err != nil {
return err
}

if destIconName == "" {
destIconName = iconName
}

// 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 dir := filepath.Dir(icoFile); !fs.DirExists(dir) {
if err := fs.MkDirs(dir, 0755); err != nil {
Expand Down
Loading

0 comments on commit 6c46f6b

Please sign in to comment.