macOS でブラウザを開く
macOS でブラウザを開きたい!と思って検索すると下のようなコードが引っかかると思います。
_system(PAnsiChar('open ' + AnsiString(URL)));
macOS は POSIX 準拠 OS なので、POSIX の system 関数経由で open
コマンドが使えます。
つまり、macOS では POSIX API を使えば簡単にブラウザが開けるのでこれでええじゃろ、という事です。
でも!本当に大丈夫なんでしょうか!
実験
さて、それでは Windows と macOS で下記の URL にアクセスしてみます。
https://google.co.jp/$1234
これは存在しないパスです。
Google は存在しないパスにアクセスするとエラーページでこんなパスないよ~って表示してくれるので、今回はその機能を利用させて貰います。
(この機能を利用するだけで、存在しないパスだけの問題ではないです)
上記の URL にアクセスするコードは以下です。
procedure TForm1.Button1Click(Sender: TObject);
begin
const URL = 'https://google.co.jp/$1234'; // URL
// Windows 版
{$IFDEF MSWINDOWS}
ShellExecute(0, 'open', PChar(URL), nil, nil, SW_SHOW);
{$ENDIF}
// macOS 版
{$IFDEF OSX}
_system(PAnsiChar(AnsiString('open ' + iURL.QuotedString('"'))))
{$ENDIF}
end;
Windows 版で使っている ShellExecute も system 関数と大体同じ機能を持つ API ですね。
実行結果
Windows での結果
/$1234
なんていうパスないよ~って言ってますね。
期待通りです。
macOS での結果
!?
/234
なんてパスないよ~って言っています!
期待値と違いますね!?
macOS の system 関数にはバグがある
上の結果の通り macOS の system 関数にはバグがあります。
確認した限り
- /$
- /%
というパスの時に/
の後ろの文字が2つ消えるという物です。
https://google.co.jp/$1234
↓ /
の後ろの文字が2つ消える ($ と後ろの文字が消える)
https://google.co.jp/234
/$ はまだしも URLEncode した URL では、/% なんて容易にありうるパスです。
これは致命的です。
この動作は macOS の system 関数が愚直に shell を呼び出しているせいで $ などの特殊文字が効いてしまっているからかもしれません。
試しに Terminal で open "https://google.com/$1234"
とすると別の結果1が得られるため、system 関数に問題がありそうです。
macOS での正しいブラウザの開き方
ずばり↓こうです。
// uses には Macapi.AppKit, Macapi.Helpers を追加する
var Workspace := TNSWorkspace.Wrap(TNSWorkspace.OCClass.sharedWorkspace);
Workspace.openURL(StrToNSUrl(iURL));
NSWorksupace の openURL メソッドを使います。
たったこれだけ。
POSIX に負けず劣らず簡単ですね。
まとめ
郷に入っては郷に従え、macOS ネイティブの API がある場合はそれを使った方が良さそうです。
おまけ
Windows, macOS, Android, iOS, Linux 全てに対応したコードです。
- Windows は ShellExecute を使い、失敗した場合は WinExec で explorer を呼び出します。
- macOS は前述の通り、NSWorkspace.openURL を使っています。
- Android は暗黙的 Intent を使っています。
- iOS は UIApplication.openURL が deprecated になっているため、自前で openURL:options:completionHandler: を定義して使っています(定義さえしてしまえば OS の API を自由に使えるのも Delphi の良いところですね)
- Linux は xdg-utils の xdg-open を使っています。
(*
* Browser Utils
*
* PLATFORMS
* Windows / macOS / iOS / Android / Linux (needs xdg-utils)
*
* USAGE
* OpenBrowser with URL
* TBrowserUtils.Open('URL');
*
* LICENSE
* Copyright (c) 2015, 2021 HOSOKAWA Jun
* Released under the MIT license
* http://opensource.org/licenses/mit-license.php
*
* HISTORY
* 2015/11/27 Ver 1.0.0
* 2021/01/20 Ver 1.0.1 macOS: _system open -> NSWorkspace.openURL
* 2021/01/30 Ver 1.0.2 iOS: openURL -> openURL:options:completionHandler:
* 2021/01/31 Ver 1.1.0 Global procedure -> record
*
* Programmed by HOSOKAWA Jun (twitter: @pik)
*)
unit PK.Utils.Browser;
interface
type
TBrowserUtils = record
public
class procedure Open(const iURL: String); static;
end;
implementation
uses
System.SysUtils
{$IFDEF MSWINDOWS}
, Winapi.Windows
, Winapi.ShellAPI
{$ENDIF}
{$IFDEF OSX}
, Macapi.AppKit
, Macapi.Helpers
{$ENDIF}
{$IFDEF ANDROID}
, Androidapi.JNI.Net
, Androidapi.JNI.App
, Androidapi.JNI.GraphicsContentViewText
, Androidapi.Helpers
, FMX.Helpers.Android
{$ENDIF}
{$IFDEF IOS}
, iOSapi.Foundation
, iOSapi.UIKit
, Macapi.Helpers
, Macapi.ObjectiveC
, FMX.Helpers.IOS
{$ENDIF}
{$IFDEF LINUX}
, Posix.StdLib
{$ENDIF}
;
{$IFDEF IOS}
type
// openURL は iOS 10.0 で deprecated になり
// openURL:options:completionHandler: に変更しなくてはならなくなった
// だが 2021/2 現在の Delphi 10.4.1 の UIApplication では
// openURL:options:completionHandler: は定義されていないため自分で定義する
UIApplicationEx = interface(UIApplication)
[MethodName('openURL:options:completionHandler:')]
function OpenURLoptionsCompletionHandler(
url: NSURL;
options: Pointer;
completionHandler: Pointer): Boolean; cdecl;
end;
TUIApplicationEx =
class(TOCGenericImport<UIApplicationClass, UIApplicationEx>)
end;
{$ENDIF}
class procedure TBrowserUtils.Open(const iURL: String);
begin
{$IFDEF MSWINDOWS}
if (ShellExecute(0, 'open', PChar(iURL), nil, nil, SW_SHOW) < 32) then
WinExec(PAnsiChar(AnsiString('explorer ' + iURL)), SW_SHOW)
{$ENDIF}
{$IFDEF OSX}
var Workspace := TNSWorkspace.Wrap(TNSWorkspace.OCClass.sharedWorkspace);
Workspace.openURL(StrToNSUrl(iURL));
{$ENDIF}
{$IFDEF ANDROID}
var Intent :=
TJIntent.JavaClass.init(
TJIntent.JavaClass.ACTION_VIEW,
StrToJURI(iURL));
{$IF RTLVersion < 30}
SharedActivity.startActivity(Intent);
{$ELSE}
TAndroidHelper.Activity.startActivity(Intent);
{$ENDIF}
{$ENDIF}
{$IFDEF IOS}
var App := TUIApplicationEx.Wrap(TUIApplication.OCClass.sharedApplication);
var URI := TNSURL.Wrap(TNSURL.OCClass.URLWithString(StrToNSStr(iURL)));
if TOSVersion.Check(10, 0) then
begin
var Dic := TNSDictionary.OCClass.dictionary;
App.OpenURLoptionsCompletionHandler(URI, Dic, nil);
end
else
App.OpenURL(URI);
{$ENDIF}
{$IFDEF LINUX}
_system(PAnsiChar('xdg-open '+ AnsiString(iURL.QuotedString('"'))));
{$ENDIF}
end;
end.
-
404 Error になるもののトップページが表示される ↩