MacVRefNum and Monterey

Hello,

I have a window named CheckForVolumesWin which displays the icon for every mounted volume and if a volume is unmounted that icon disappears.
It worked well in Xojo 2019 Release 1 but I upgraded to macOS Monterey and Xojo 2019 Release 1 is not compatible so I am trying it in Xojo 2021 Release 2.1 but I am getting this error…

CheckForVolumesWin.CheckForVolumes, line 20
Type “FolderItem” has no member named “MacVRefNum”
j = Volume(i).MacVRefNum 'This is the key to differentiate 2 volumes (e.g, 2 volumes can be named “Macintosh HD”, with a different icon, and we want to remove the right one).

MacVRefNum was deprecated in version 2010r5. There is no replacement.

Here is the code:

Public Sub CheckForVolumes()
  'Self.Width = 80
  dim b As Boolean
  Dim i,n as Integer
  dim j,k,l As Integer
  Dim NumberOfRows as integer
  Dim IW as CImageWell
  Dim ST as CStaticText
  Dim S as String
  S = ""
  n = VolumeCount
  
  NumberOfRows = Ceil(n/7)
  'MsgBox "There will be " + Str(NumberOfRows) + " rows of mounted volumes"
  
  if UBound(ListOfVolumes) = n-1 then Return 'No change
  
  if UBound(ListOfVolumes) < n-1 then 'Volume mounted
    //Here, we're going to find which volume is not in the list (the list is built every time a volume is added to the window):
    For i = 0 to n-1
      j = Volume(i).MacVRefNum 'This is the key to differentiate 2 volumes (e.g, 2 volumes can be named "Macintosh HD", with a different icon, and we want to remove the right one).
      if ListOfVolumes.IndexOf(j)=-1 then 'Not in the known list, add it
        IW = New myImageWell
        IW.name = Volume(i).Name
        
        If i < 7 then
          IW.Top = 5
          IW.Left = 16 + 69*(i)
          IW.Width = 48
          IW.Height = 48
          IW.tag=Volume(i).MacVRefNum 'keep a reference (tag is from the defined class)
          'S = S + chr(13) + "volume(" + str(i) + ") is " + IW.name + chr(13)
          
          ST = New myStaticText
          ST.name = Volume(i).Name + "ST"
          ST.Top = 55
          ST.Left = 10 + 69*(i)
          ST.Width = 60
          ST.Height = 12
          ST.Text = Volume(i).Name.Left(4) + "..." + Volume(i).Name.Right(4)
          ST.tag=Volume(i).MacVRefNum 'keep a reference (tag is from the defined class)
          
        elseif i < 14 then
          IW.Top = 5 + 76
          IW.Left = 16 + 69*(i-7)
          IW.Width = 48
          IW.Height = 48
          IW.tag=Volume(i).MacVRefNum 'keep a reference (tag is from the defined class)
          'S = S + chr(13) + "volume(" + str(i) + ") is " + IW.name + chr(13)
          
          CheckForVolumesWin.Height = 75 + 80
          
          
          ST = New myStaticText
          ST.name = Volume(i).Name + "ST"
          ST.Top = 55 + 76
          ST.Left = 10 + 69*(i-7)
          ST.Width = 60
          ST.Height = 12
          ST.Text = Volume(i).Name.Left(4) + "..." + Volume(i).Name.Right(4)
          ST.tag=Volume(i).MacVRefNum 'keep a reference (tag is from the defined class)
          
        elseif i < 21 then
          IW.Top = 5 + 76 + 76
          IW.Left = 16 + 69*(i-7*2)
          IW.Width = 48
          IW.Height = 48
          IW.tag=Volume(i).MacVRefNum 'keep a reference (tag is from the defined class)
          'S = S + chr(13) + "volume(" + str(i) + ") is " + IW.name + chr(13)
          
          CheckForVolumesWin.Height = 75 + 80*2
          
          
          ST = New myStaticText
          ST.name = Volume(i).Name + "ST"
          ST.Top = 55 + 76 + 76
          ST.Left = 10 + 69*(i-7*2)
          ST.Width = 60
          ST.Height = 12
          ST.Text = Volume(i).Name.Left(4) + "..." + Volume(i).Name.Right(4)
          ST.tag=Volume(i).MacVRefNum 'keep a reference (tag is from the defined class)
          
        end if
        
        
        Dim f as FolderItem
        f = Volume(i)
        Dim icon as MacIcon = MacIcon.NewIconFromFolderItem(f)
        icon.size = 40
        
        Dim p as Picture = icon
        IW.Image = p
        ListOfVolumes.Append j 'Add the volume to our list so we know it's known by us.
      end if
    Next
  else 'volume ejected
    //Determine which volume has been unmounted (which one is still in our list, but not in the volumes() array).
    k=UBound(ListOfVolumes)
    for i=k DownTo 0 'Reverse order in case 2 volumes have been ejected
      b=False 'if b remains false, it means the volume was not in the list (see below)
      for j=0 to n-1
        if Volume(j).MacVRefNum=ListOfVolumes(i) then
          b=true 'volume is in the list (still mounted and valid)
          Exit
        end if
      Next
      
      if not b then 'we found which volume has been ejected.
        //Here, we won't delete static texts or image wells, because it will mess the correct ordering (e.g, say we have 5 disks, static texts and image wells and we remove the 2nd.
        //Then, on new disk insertion, the new static text and image well will have an index of 2, which we don't want since the disk is the last in the list).
        
        //Instead of deleting, we will first look for the item that was ejected, copy every static text and image well from there (the i+2 becomes the i+1, the i+3 becomes the i+2, etc)
        //and delete the last one (we keep the same ordering).
        
        l=0
        b=False
        do
          IW=myImageWell(l)
          if IW=nil then 'We are beyond the bounds (last index is l-1)
            myImageWell(l-1).Close
            myStaticText(l-1).Close
            Exit 'do with next volume
          end if
          if b then 'we found the volume that was ejected. We "push up" every static text and image well to make the last one the one we will remove.).Image
            myImageWell(l-1).Image=myImageWell(l).Image
            myStaticText(l-1).Text=myStaticText(l).Text
            myImageWell(l-1).Tag=myImageWell(l).Tag
            myStaticText(l-1).Tag=myStaticText(l).Tag
          end if
          if IW.Tag=ListOfVolumes(i) then
            b=True 'happens when the item is the one we ewants (the one which was ejected). This actually happens "before" the block of code just above ("if b then"), but on the next iteration of the loop
          end if
          l=l+1
        loop
        ListOfVolumes.Remove i
      end if
    Next
  end if
  
  
  self.Refresh
  
  'Self.Width = 80 + 69*(UBound(ListOfVolumes))
  Self.Width = 485
  'Self.Left  = Window1.Left
  
  'EditField1.Text = Trim(S)
  CheckForVolumesWin.refresh 'We don't know it anymore
End Sub

How can I fix that?

Thanks.

Lennox

Is this just a matter of running Xojo 2019r1?
Can you install a VM with older macOS to keep using 2019r1?
Do you know about declares? I found this information but I have never used declares:
https://ohanaware.com/blog/202035/Building-a-better-Mac-app-with-Xojo-volumes.html

1 Like

FWIW, you can differentiate volumes, even with the same name, using the native path. A 2nd “Macintosh HD” will have “/Volumes/Macintosh HD-1” as its path (if the 1st one is the startup disk, you’ll have “/” for the startup disk and “/Volumes/Macintosh HD” for the other, guaranteed to be different).

You could stay with using MacVRefNum and just continue to use it.

If it gets removed, you can switch to using GetVolumeRefMBS function in MBS Xojo Plugins.

See statfs declare how to get the volume RefNum via declare.
No expensive plugin needed.

1 Like

Hi Arnaud,

I replaced all instances of MacVRefNum with NativePath, but NativePath is a string and MacVRefNum is an integer so it is not compiling.

How can I fix that?

Thanks.

Lennox

Thanks but I have upgraded to Monterey already.
Lennox

Thanks, looked at it, but I am not proficient with declares.
Lennox

From what I read is not hard to create a VM on your Monterey installation to run older macOS versions.

Anyway, did you try Xojo 2019r3.2? is MacVRefNum removed there? That Xojo version should run in Monterey.

I have tried VM before and did not particularly like it.

I am trying Xojo 2021r2.1 with macOS Monterey Version 12.6.3 (21G419) on Mac Mini with plans to upgrade to Xojo 2023 when it comes out.

Lennox

It is not that hard.
Make a new project and drop a Text Area in the Window.
In its open Event put following code

For i As Integer = 0 To FolderItem.LastDriveIndex
  Dim path As String = FolderItem.DriveAt(i).NativePath
  Me.AddText "*** " + path + " ***" + EndOfLine
  Me.AddText statfsForPath(path) + EndOfLine
Next

Now add a Method to the Window:

Public Function statfsForPath(path as String) As String
  Soft Declare Function statfs Lib "libc.dylib" (path As Ptr, buff As Ptr) As Int32
  
  Const statfsStructSize = 2168 //sizeof(struct statfs): 2168
  Const MAXPATHLEN     = 1024
  Dim statfsResult As New MemoryBlock(statfsStructSize)
  Dim lines() As String
  
  Dim pathBuf As New MemoryBlock(LenB(path)+1)
  pathBuf.StringValue(0, LenB(path)) = path
  
  Dim result As Integer = statfs(pathBuf, statfsResult)
  If result <> 0 Then
    lines.append "Error "+Str(result) + EndOfLine
    Return String.FromArray(lines, EndOfLine)
  End
  
  Const MFSTYPENAMELEN = 16
  Const MNAMELEN       = MAXPATHLEN
  Dim pos As Int32 = 0
  
  lines.append "f_bsize:  """ + statfsResult.UInt32Value(pos).tostring + """" + EndOfLine
  pos = pos + 4
  lines.append "f_iosize: """ + statfsResult.Int32Value(pos).tostring + """" + EndOfLine
  pos = pos + 4
  lines.append "f_blocks: """ + statfsResult.UInt64Value(pos).tostring + """" + EndOfLine
  pos = pos + 8
  lines.append "f_bfree:  """ + statfsResult.UInt64Value(pos).tostring + """" + EndOfLine
  pos = pos + 8
  lines.append "f_bavail: """ + statfsResult.UInt64Value(pos).tostring + """" + EndOfLine
  pos = pos + 8
  lines.append "f_files:  """ + statfsResult.UInt64Value(pos).tostring + """" + EndOfLine
  pos = pos + 8
  lines.append "f_ffree:  """ + statfsResult.UInt64Value(pos).tostring + """" + EndOfLine
  pos = pos + 8
  lines.append "fsid_:    """ + statfsResult.UInt32Value(pos).tostring + """" + EndOfLine
  pos = pos + 4
  lines.append "f_owner:  """ + statfsResult.UInt32Value(pos).tostring + """" + EndOfLine
  pos = pos + 4
  lines.append "f_type:   """ + statfsResult.UInt32Value(pos).tostring + """" + EndOfLine
  pos = pos + 4
  lines.append "f_flags:  """ + statfsResult.UInt32Value(pos).tostring + """" + EndOfLine
  pos = pos + 4
  lines.append "f_fssubtype: """ + statfsResult.UInt32Value(pos).tostring + """" + EndOfLine
  pos = pos + 4
  lines.append "f_fstypename[MFSTYPENAMELEN]: """ + DefineEncoding(statfsResult.StringValue(pos, MFSTYPENAMELEN), Encodings.UTF8) + """" + EndOfLine
  pos = pos + MFSTYPENAMELEN
  lines.append "f_mntonname[MAXPATHLEN]:      """ + DefineEncoding(statfsResult.StringValue(pos, MAXPATHLEN), Encodings.UTF8) + """" + EndOfLine
  pos = pos + MAXPATHLEN
  lines.append "f_mntonname[MAXPATHLEN]:      """ + DefineEncoding(statfsResult.StringValue(pos, MAXPATHLEN), Encodings.UTF8) + """" + EndOfLine
  
  Return String.FromArray(lines, "")
End Function

You are interested in the “fsid_” field of the returned values.
Just modify the code to return this field as UInt32.

1 Like

Thanks TomE,

I created the app as you stated, it compiled without any issues, and I got a value for the fsid_: “2200968259”.

I will now apply that to my app and let you know.

Thanks again.

Lennox

Hi TomE,
I got an fsid_: value using your suggestion for all of the mounted volumes but I just don’t know how to use them in my app.
Kindly advise.

Thanks.
Lennox

OK TomE,

I got the Volume(i).MacVRefNum by adding this to the bottom of the code in the TextArea

Me.Text = ReplaceLineEndings(Me.Text, EndOfLine.macOS)

Dim MyArray() as string
Dim Thefsid as string
Dim ThefsidString1 , ThefsidString2 as string

MyArray = Split(Me.Text,EndOfLine.macOS)
For i as integer = 0 to UBound(MyArray)
  Thefsid = MyArray(i)
  If Thefsid.Left(6) = "fsid_:" then
    ThefsidString1 = nthfield(MyArray(i), "fsid_:", 2)
    ThefsidString1 = ReplaceAll(ThefsidString1, ThefsidString1.Left(1), "")
    ThefsidString1 = ReplaceAll(ThefsidString1, ThefsidString1.Right(1), "")
    ThefsidString2 = ThefsidString2 + EndOfLine.macOS + ThefsidString1
  end if
next i
msgbox ThefsidString2

Me.Text = Me.Text + EndOfLine.macOS + ThefsidString2

Now I have to incorporate that to my app.

Is there a better/more elegant way to do it?

Thanks.

Lennox

Hi Lennox,
my code was just an example how to get all fields of the returned statfs data. You can just return the needed field like this:

Public Function statfs_fsid(path as String) As UInt32
  Soft Declare Function statfs Lib "libc.dylib" (path As Ptr, buff As Ptr) As Int32  
  Const statfsStructSize = 2168 //sizeof(struct statfs): 2168
  Dim statfsResult As New MemoryBlock(statfsStructSize)
  
  Dim pathBuf As New MemoryBlock(LenB(path)+1)
  pathBuf.StringValue(0, LenB(path)) = path
  Dim result As Integer = statfs(pathBuf, statfsResult)
  If result <> 0 Then
    Dim r As New RuntimeException
    r.Message = "Error "+Str(result)
    Raise r
  End
  Dim pos As Int32 =  48
  Return statfsResult.UInt32Value(pos)  
End Function

Thanks TomE,

I had to revert to my original code and use Nativepath instead of MacVRefNum and all is well now.

Thanks for your code, I may have to use it sometime in the future.

Thanks again.

and thanks again to Arnaud N.

You’re welcome.