openURL broken on MAC

help = File.join(File.dirname(__FILE__), 'myextensions', 'help.html')
UI.openURL("file:///#{help}")

This function has worked for years. Now I have a MAC user who says it’s broken in SketchUp 2019. No error message, just nothing happens when he clicks on my Help icon.
He reports another extension that has a function to open Finder that is also broken.
I’m a windows developer so cannot test. Has anyone else seen this?

As of SU 2019.3, OpenURL no longer encodes its argument for you. In particular if the URL string contains any spaces you must replace them with %20, the URL code for space. Other special characters could also need encoding, so print out the string to check.

Steve, do you know the background of why this was changed ?

@barry_milliken_droid See …

require 'uri'

# Default replacement:
path = URI.encode("file:///C:/Program Files/SketchUp/Some Subfolder/@!help.html")
#=> file:///C:/Program%20Files/SketchUp/Some%20Subfolder/@!help.html

# Specify a character set to replace as 2nd argument:
path = URI.encode(
  "file:///C:/Program Files/SketchUp/Some Subfolder/@!help.html",
  " !@" # ie: space, exclamation point, asterisk
)
#=> file:///C:/Program%20Files/SketchUp/Some%20Subfolder/%40%21help.html

NOTE: These methods have a note that they are obsolete and that there are alternatives like CGI.escape and URI.encode_www_form_component, but they both want to change spaces to “+” which might not work well. (I’ve no Mac so Steve’ll need to weight in.)

So either:
All extensions that might use this function must be changed and redistributed.
OR
Trimble need to fix it for version 19.4

I vote for number 2.

If I have to do number one, where do I go to learn about encoding all special characters?

They fixed it because of a reason. The Release Notes say simply …

Fixed UI.openURL to not perform URL encoding on Mac for cross platform consistency.

… but there was a problem where encoded URLs could not find valid resources on the Mac.
(Might have been something that began happening with the Catalina release. :shrug: )

Right where I said above.

1 Like

links that open in the default browser and not a HtmlDialog should be banned…

they are really annoying…

john

1 Like

I tend to always use system commands to open files (or folders)


# OSX vs WINDOWS
PLATFORM_IS_OSX     = ((Object::RUBY_PLATFORM =~ /darwin/i) ? true : false).freeze()
PLATFORM_IS_WINDOWS = (!PLATFORM_IS_OSX).freeze()

# get folder path and do repl
folder_path = "path/to/the/folder"
folder_path =  folder_path.gsub("/", "\\\\" if PLATFORM_IS_WINDOWS 
# construct the command to open Explorer on Windows or Finder on OsX
command = PLATFORM_IS_WINDOWS ? "start \"\" \"#{folder_path}\"" : "open \"#{folder_path}\"" 

Edit: shoot, missed the most important line :slight_smile:

# launch the command
system(command)
2 Likes

It was inconsistent across platforms. And it prevented URLs with fragments # to load properly because the # was URL encoded (among other characters).

Since Ruby provide encoding capabilities out of the box we made it so both platforms would require the API user to provide a correctly formatted URL.

Got a code example for this?

1 Like

Could this fix for cross-platform inconsistencies with URLs have introduced a platform difference for local paths? I’ve used openURL quite a few times to open local documents.

This is btw the solution I use now.

The URL specs say either %20 or + may substitute for space.

Edit: After some more research, it seems that there has been confusion about this for some time. The established practice when encoding the request part (after the ?) in an HTML GET or POST request was to use +. So, using + instead of %20 is still generally allowed in that context. But for the path part of the URL it seems to be preferred to use %20 for consistency with how other illegal characters are encoded.

The change in behavior in UI.openURL jumped out on Mac because the normal path for extension files is

/Users/username/Library/Application Support/SketchUp 2019/SketchUp/Plugins

which contains two spaces and will no longer work without encoding.

Replies;

  1. The “PLATFORM_IS” approach is extremely problematic to any developer who does have both platforms to test. You make the change, assemble all the code, upload it to the warehouse, thousands of users download it, then you see typo that you couldn’t test.
  2. Opening a 30 page user manual in the default browser is much better than using HTML dialog. The help file can contain links to other websites, and the user has browser support for search within the document.
  3. Before Trimble allowed such a change, they should have performed a global search for OpenURL in all ruby code in the warehouse (I expect that they keep the unencrypted ruby) and send a red alert email to every developer. Another indication that adult supervision is lacking.
1 Like

Why?
Just create a method that does this good for once and for all and just call that method from everywhere in your code…


# somewhere in constants.rb f.e.
# OSX vs WINDOWS
PLATFORM_IS_OSX     = ((Object::RUBY_PLATFORM =~ /darwin/i) ? true : false).freeze()
PLATFORM_IS_WINDOWS = (!PLATFORM_IS_OSX).freeze()

# somewhere in utils.rb f.e.
def open_external(path)
  # do replace on windows
  path=  path.gsub("/", "\\\\") if PLATFORM_IS_WINDOWS 
  # construct the command to open Explorer on Windows or Finder on OsX
  command = PLATFORM_IS_WINDOWS ? "start \"\" \"#{path}\"" : "open \"#{path}\""
  system(command)
end #def

# somewhere in your code
open_external("C:/users/") # this should work both for folders and files

And somehow every new ruby developer will have to trip over this problem, beg for help, then finally solve it “once and for all” for himself.

Your approach also shows a lack of “adult supervision”.

I am afraid I am not getting it.

1 Like

It means being aware of the big picture. “Solutions” like yours are what the API is for.
It maybe it should have a method “OpenURLfromSU” well tested by Trimble on both platforms to do thus.
It should not have to be reinvented and propagated by every developer.

if you check the API or Julia’s link. you’ll see that SU does have a platform method…

Sketchup.platform == :platform_win
# or
Sketchup.platform == :platform_osx

and as you don’t even need a url to open a html file in a browser or any in Finder on a mac, you set yourself up for a fall…

help = File.join(File.dirname(__FILE__), 'myextensions', 'help.html')

if Sketchup.platform == :platform_win
  UI.openURL("file:///#{help}")
else # use one of the incredibly common, standard Ruby methods
  # all these work
  `open "#{help}"`
  # or
  %x[ open "#{help}" ]
  # or
  system("open #{help.inspect}")
  # there are even more variants, this opens any file type in Finder...
  %x[ open -R #{help.inspect}]  
end

there is no need to reinvent the wheel if carry out some ‘due diligence’ before publishing…

john

1 Like

Hmm… opening a file is something very common and should’nt even be part of a sketchup specific api imho. So maybe it is not reinventing the wheel…

1 Like

Never noticed it. Got added in 2014 it seems. The check I use has been stuck on top of my constants.rb file for ages I believe.
Will change my starter kit to use the sketchup specific method. Thanks.

RE: openURL broken on MAC - #13 by kengey, post 13

A few observations …
path.gsub("/", "\\\\") if PLATFORM_IS_WINDOWS

It’s totally unnecessary to replace forward unix-like slashes with double backslashes on Windows. Windows NT has always AFAIK accepted pathnames with forward slashes. I usually do the opposite, replacing backslashes with forward slashes (there are some Sketchup module methods that return double backslashed pathnames on Windows, but shouldn’t have been written this way.) …

path = path.gsub(/(\\\\|\\)/,'/') if PLATFORM_IS_WIN

I think the main exception is registry hive paths.


PLATFORM_IS_OSX = ((Object::RUBY_PLATFORM =~ /darwin/i) ? true : false).freeze()
  • RUBY_PLATFORM being a constant defined in Object, is global.
    The class qualification is not necessary.

  • =~ does not return a boolean true || false so you use a tertiary IF, but instead it’s better (more elegant) to do the opposite and use !~ method which does directly return true || false so no tertiary IF is needed (ie., it’s faster.) Ex …

    PLATFORM_IS_WIN = RUBY_PLATFORM !~ /darwin/i
    PLATFORM_IS_OSX = !PLATFORM_IS_WIN
    
  • The Sketchup.platform method came out for SU2014 but for backward compatibility you can use a rescue clause when the Sketchup module doesn’t respond to a ::platform() call. IE …

    PLATFORM_IS_WIN =(
     Sketchup.platform == :platform_win rescue RUBY_PLATFORM !~ /darwin/i
    )
    PLATFORM_IS_OSX = !PLATFORM_IS_WIN
    
  • Freezing / Constants are not really constant. Ie …

    EDIT (ADD): Trying to freeze an immediate singleton object like true or false is a frivolous endeavor.

    PLATFORM_IS_OSX = !PLATFORM_IS_WIN.freeze()
    

    Then do this …

    PLATFORM_IS_OSX = "I've been changed!"
    

    You get a warning that the constant was previously set, but it is still reassigned to point at the new object (in this case a String.) The global = reference assignment function is an interpreter function, not a Ruby method, so it can reassign references that point at frozen objects so that they point at other objects (frozen or not.)
    So to truly freeze your constants, you must wrap them within a nested mixin module, freeze that module, and then mixin (via include) this module wherever you need to access these constants.

    module Author
      module SomePlugin
    
        module Constants
          PLATFORM_IS_WIN =(
            Sketchup.platform == :platform_win rescue RUBY_PLATFORM !~ /darwin/i
          )
          PLATFORM_IS_OSX = !PLATFORM_IS_WIN
        end
        Constants.freeze
    
        include Constants
        
        # Plugin module code here can access Constants.constants
    
      end
    end
    

    Or perhaps the constants are in a separate file oustide the current plugin module. Ex …

    # constants.rb
    module Author
      module Constants
        PLATFORM_IS_WIN =(
          Sketchup.platform == :platform_win rescue RUBY_PLATFORM !~ /darwin/i
        )
        PLATFORM_IS_OSX = !PLATFORM_IS_WIN
      end
      Constants.freeze
    end
    
    # plugin_main.rb
    require 'constants.rb'
    module Author
     module SomePlugin
    
       include Author::Constants
       
       # Plugin module code here can access Author::Constants.constants
    
     end
    end
    

Just my 5 cents worth … :wink: