Howto register file extensions and open the files

Mac OSX specific forum
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Howto register file extensions and open the files

Post by freak »

If you want to do one of these things with PB for OSX, then this post is for you:
- associate file extensions to open with your app on doubleclick
- associate an icon with your file extension
- allow files to be opened by draging them to your app icon

Step 1: code

Unlike on other OS, the files to open are not passed on the commandline but through events after the program has launched. This results in more code to catch these events than reading the commanline, but the positive point is that this way you receive a file event even when the user drags/doubleclicks a file after he started your program.

Here is the code to catch the event and extract the filenames:

Code: Select all

;
; Receive the OpenDocument event on OSX
;

; Some required definitions
;
#eventNotHandledErr = -9874

#CoreEventClass = 'aevt'
#AEOpenDocuments = 'odoc'
 
#typeFSS = 'fss ' 
#typeAEList = 'list'
#typeFSRef = 'fsrf'
#keyDirectObject = '----'

#typeNull = 'null' 
#noErr = 0

Structure FSRef
  hidden.b[80]
EndStructure

Structure AEDesc
  desctiptorType.l
  dataHandle.l
EndStructure

; ProcedureCDLL is important, for the right calling convention (on x86) and
; the correct callback behavior (therefore the DLL part)
;
ProcedureCDLL OpenDocument(*appleEvt, *reply, refcon)
 
  ; extract the event parameters
  If AECreateDesc_(#typeNull, 0, 0, @documents.AEDesc) = #noErr
    If AEGetParamDesc_(*appleEvt, #keyDirectObject, #typeAEList, @documents)  = #noErr
      If AECountItems_(@documents, @n) = 0

        *Buffer = AllocateMemory(1000)
        If *Buffer

          ; There can be multiple files sent
          For i = 1 To n
            If AEGetNthPtr_(@documents, i, #typeFSRef, @keyWd, @typeCd, @fileRef.FSRef, SizeOf(FSRef), @actualSize) = #noErr
              If FSRefMakePath_(@fileRef, *Buffer, 1000) = #noErr

                File$ = PeekS(*Buffer, -1, #PB_Ascii)
                ;
                ; If you want to accepte directories too, then change this
                ;
                If FileSize(File$) >= 0 
                  ;
                  ; Here you have the filename to open
                  ;
                  AddGadgetItem(0, -1, File$)
                                   
                EndIf
                
              EndIf 
            EndIf
          Next i
        
          FreeMemory(*Buffer)
        EndIf
        
      EndIf
    EndIf
    AEDisposeDesc_(@documents)
  EndIf
 
EndProcedure



If OpenWindow(0, 0, 0, 600, 300, "Open Document test", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
  ListViewGadget(0, 10, 10, 580, 280)
  
  ; Call this before processing the first window events, else you may miss the open event!
  ;
  If AEInstallEventHandler_(#CoreEventClass, #AEOpenDocuments, @OpenDocument(), 0, 0) <> #NoErr
    AddGadgetItem(0, -1, "cannot install event handler!")
  EndIf
  
  Repeat
  Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

End
If you run this, you will notice that you cannot drag any files to the application. That is because you need to tell finder what types of files you accept. This leads us to the next step...

Step 2: configuration

To tell finder about your file types, edit the "Info.plist" in your bundle directory.
- Compile the above code to a .app
- Select the .app in finder.
- Open the context-menu and select "Show package contents" (this will display the contents of the .app directory)
- Open the Info.plist file in the Contents folder as described below.

Note: The compiler overwrites this file on every compilation, so better save your modifications elsewhere and copy the file back after every new compilation. (You can also automate this by creating a small tool that is run after the executable is created)
Keep in mind that if you make some changes in the compiler options, you have to apply the resulting changes in the generated file also to your saved copy. (mainly the CFBundleExecutable and CFBundleIconFile keys)

Note2: The changes you make to the file may not immediately be seen by the finder. Especially if you previously executed the program with different settings. It sometimes helps to create the .app in a new location or copying the .app to a different folder for finder to notice the changes. In some cases only a restart will make the changes take effect. (happened to me with setting an icon for the files)

An example first:
This is what we add to the Info.plist for the IDE (it goes into the main <dict> tag)

Code: Select all

  <key>CFBundleDocumentTypes</key>
  <array>
    <dict>
      <key>CFBundleTypeExtensions</key>
      <array>
        <string>pb</string>
        <string>pbi</string>
      </array>
      <key>CFBundleTypeIconFile</key>
      <string>FileIcon.icns</string>
      <key>CFBundleTypeName</key>
      <string>PureBasic Sourcecode</string>
      <key>CFBundleTypeRole</key>
      <string>Editor</string>
    </dict>
  </array>
Another example:
Use this if you just want any file to be dragable to your application icon, but no specific filetype to be registered:

Code: Select all

	<key>CFBundleDocumentTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeExtensions</key>
			<array>
				<string>*</string>
			</array>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
		</dict>
  </array>
- As you can see you can have multiple extensions in the CFBundleTypeExtensions array
- You can also have multiple versions of the entre <dict> part, to associate different extensions with different icons for example
- You need to specify atleast the CFBundleTypeExtensions and CFBundleTypeRole parts, the rest is optional

Here is a short description of the fields:

CFBundleTypeExtensions (required)
Specifies the list of file extensions. The <array> is needed, even if there is only on extension.
To make this setting apply for all types (for example if you want any file to be droppable to your app icon), use a "*".

CFBundleTypeIconFile
Here you can set an icon file for these file extensions. The icon must be copied to the Contents/Resources folder of the .app

CFBundleTypeName
Set a name to describe your file type here.

CFBundleTypeRole (required)
Specify what your application does with the type. Values can be "Editor", "Viewer", "Shell" or "None".

All available keys for the Info.plist are explained in detail here:
http://developer.apple.com/documentatio ... tKeys.html

There are some other keys that may be of intrest to you, like the CFBundleGetInfoString one, which allows to set a string that can be seen when the user selects "Get Info" for your .app.
quidquid Latine dictum sit altum videtur
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Post by WilliamL »

I'm trying your example and it works fine but I have a question about icons.

How do I create icons?

[later after going to the Apple site]

This is what Apple says: (explains how icons are made in a drawing program - .png)

http://developer.apple.com/documentatio ... ion_8.html

Then Icon Composer... (how to take your drawing and make it an icon)

http://developer.apple.com/documentatio ... ion_5.html
Last edited by WilliamL on Fri Jan 09, 2009 9:22 pm, edited 3 times in total.
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
freak
PureBasic Team
PureBasic Team
Posts: 5940
Joined: Fri Apr 25, 2003 5:21 pm
Location: Germany

Post by freak »

Just make differently sized png files with the tool of your choise and then use the icon composer (comes with the development tools, see your link) to put them together into a .icns file.
quidquid Latine dictum sit altum videtur
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Post by WilliamL »

Yeah, it works!

The process of the mask and all is explained in the first of my links but the mask doesn't seem to do anything. I did discover that the icons have to be put in a Resources folder in the Contents folder. Also that the Info.plist can be copied at the same location as the original plist and then the copy can be used to replace the new plist when re-compiling (is that clear?).

At first it seems very intimidating but after reading my second link about Icon Composer it was very simple. Certainly easier than using resources!

It does take awhile (by logging-out/re-starting/phases of the moon) for the new icons to appear.
Last edited by WilliamL on Fri Jan 09, 2009 9:28 pm, edited 2 times in total.
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Post by WilliamL »

ok, question, how do I make the 'hit mask' (or just the 'mask')? All I get is a black box when I view the Masks in Icon Composer.

[later] Oh, I see, the background of the icon has to be transparent.

The app icon can be changed using:

<key>CFBundleIconFile</key>
<string>AppIcon.icns</string>

PLists are a whole topic by themselves.
Last edited by WilliamL on Fri Jan 09, 2009 9:21 pm, edited 1 time in total.
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Post by WilliamL »

The only reservation I have about using the normal three letter extensions(ie .txt) for identifying files is that I don't know what extensions have already been used. Using only three letters (the norm) really limits how many combinations are available. If I want to use .stx, how will I know if it is already used? I wonder if it would be useful to use four character extensions (ie .aiff) for my programs since 4 letter extensions are seldom used. I suppose if the extension is only used on my computer then I could use any length extension (or is there a limit?).

Any thoughts?
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
Wolfram
Enthusiast
Enthusiast
Posts: 604
Joined: Thu May 30, 2013 4:39 pm

Re: Howto register file extensions and open the files

Post by Wolfram »

Hi,

the Code is not working for me.
The Program opens and 0 Item its listed.
Can some help me?

Thanx
macOS Catalina 10.15.7
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: Howto register file extensions and open the files

Post by WilliamL »

Hi Wolfram,

I'm not sure what you are asking...

This is a really old thread. If you want to associate a file with an app with the new Cocoa system then there is other code to do that. (see wilbert's [PB Cocoa] Methods, Tips & Tricks - http://www.purebasic.fr/english/viewtop ... 15#p391349). If you are having problems with your plist then you can always look at the default plist that PureBasic creates with every app.
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
User avatar
Piero
Addict
Addict
Posts: 868
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Howto register file extensions and open the files

Post by Piero »

Can some "cocoa guru" take a look and "update" this?
IMHO it would be very useful…
I feel like Fred's PB_Gadget_SetOpenFinderFiles can be dramatically improved…
WilliamL
Addict
Addict
Posts: 1252
Joined: Mon Aug 04, 2008 10:56 pm
Location: Seattle, USA

Re: Howto register file extensions and open the files

Post by WilliamL »

Code: Select all

PB_Gadget_SetOpenFinderFiles
This command works differently than it did back when this thread started and (I understand) this is how it works for the IDE. Now I have to intercept the incoming files in the event loop using a PostEvent(). This was explained in this thread https://www.purebasic.fr/english/viewto ... inderFiles.

I'm (no Guru) doing something like this (it pretty much copies the code in the above thread) :)

Code: Select all

;Fred's OpenDoc (early in code)
            ImportC ""
                PB_Gadget_SetOpenFinderFiles(Callback)
            EndImport
            
            ;Enumeration CustomEvent #PB_Event_FirstCustomValue
              #MyEvent_OpenFiles=1000
            ;EndEnumeration
            
            Global FileIn$
            
            IsGadget(0) ; Ensures the gadget lib is linked as this command is in it
            ProcedureC OpenFinderFilesCallback(*Utf8Files)
                Protected filecnt,filesin$,filepath$,filename$
                filesin$ = PeekS(*Utf8Files, -1, #PB_UTF8) ; Each file is separated by a 'tab' 
                ;MessageRequester("Raw Load...",filesin$)
                If Len(filesin$) ; Here you have the filename to open
                    filein$=StringField(filesin$,1,Chr(9)) 
                EndIf
                PostEvent(#MyEvent_OpenFiles)
            EndProcedure
            PB_Gadget_SetOpenFinderFiles(@OpenFinderFilesCallback()) ; should be put very early in the code, before any event loop
            ;end of Fred's opendoc

Code: Select all

Repeat
    event = WaitWindowEvent()
    Select event
    Case #MyEvent_OpenFiles
        FilePath=GetPathPart(filein$)
        FLD$=GetFilePart(filein$)
        LoadFile(FLD$,"")
        DefaultWnd()
    EndSelect
ForEver
MacBook Pro-M1 (2021), Sequoia 15.4, PB 6.20
User avatar
Piero
Addict
Addict
Posts: 868
Joined: Sat Apr 29, 2023 6:04 pm
Location: Italy

Re: Howto register file extensions and open the files

Post by Piero »

Thanks for your answer, but I meant that "hopefully there must be some updated way to easily handle apple events in/out"
If so, it can be very useful...

PS:
https://www.purebasic.fr/english/viewtopic.php?t=82221
https://www.purebasic.fr/english/viewtopic.php?t=66750
Post Reply