[Unity] Standalone Shell and Python Integration

本篇文章主要紀錄透過 Unity 在 Mac 上製作 Standalone 工具時,整合 Shell、Python、AppleScript 以及其他第三方程式可能遇到的問題。

Overview

  1. Shell Execution:如何執行 Shell 以及進行參數設定?
  2. Script Location:要使用到的其他 Scripts 要放到哪裡?
  3. Application Location:如何定位使用者電腦某個應用程式?

1. Shell Execution

我們可以透過撰寫 shell script 來執行 python 程式檔
以下程式碼會開啟終端機並執行你指定的 shell script
注意到 Process 執行的檔案路徑最終要額外加上雙引號

但這個方法有個缺點,就是它並沒有可以傳遞參數的方式
找了半天找不到,如果你有找到麻煩留言告訴大家,造福人群!
要傳遞參數要使用 bash

public static void Execute(string fileName)
{
    var terminal = "/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal";
    var path = Path.Combine(Application.dataPath, "Scripts", fileName);
    Process.Start(terminal, $"\"{path}\"");
}

透過 bash 的話就可以順利傳遞了
但可以看到,只要參數一多,就會需要不斷更改
所以也沒有到十分理想,我最後採用的是寫入一個 config file 的方式

public static void Execute(string path, string param1, string param2)
{
	var startInfo = new ProcessStartInfo()
    {
	    FileName = "/bin/bash",
    	UseShellExecute = false,
	    CreateNoWindow = false,
    	Arguments = $"-c '\"{path}\" \"$0\" \"$1\"' \"{param1}\" \"{param2}\""
    };
    
    var process = new Process { StartInfo = startInfo };
    process.Start();
}

在 Shell Script 的部分
需要切換到 python 所在路徑,否則會無法找到檔案

srcdir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )
cd "$srcdir"; 
python script.py

2. Script Location

在第一步驟提到的 config 檔,我把它跟其他 shell 和 python scripts 放在一起
這樣子只要透過相對路徑讀取即可,不用去訂什麼特殊路徑
build 完 app 之後按右鍵顯示套件內容,把 scripts 丟到 Contents 裡面即可
你可以放 shell、python、applescript、javascript 或任何你想用的
如下圖所示,這個工具相關需要安裝的 python 和對應的 packages
可以先輸出好 requirements.txt 後,讓使用者按一個按鈕執行 shell scripts 進行安裝就好
而這一層就是對應到 Application.dataPath

standalone app


但如果是自行開發的 App,直接丟給其他使用者用而非從 Store 上下載
會被識別為未知來源的應用程式
而應用程式 root 會被 Mac 丟到一個暫存的唯讀資料夾,大概會長這樣:
/private/var/folders/.../AppTranslocation/UNIQUE_ID
這樣子的話會無法寫入 config 檔,後續也無法順利進行
要解決這個問題也很簡單
像其他 Mac 程式一樣,丟到應用程式裡面就好

3. Application Location

我們除了可以執行撰寫程式之外
也可以透過 applescript 來呼叫其他程式執行
但是,要怎麼知道使用者目前安裝了什麼程式?

以筆者的需求為例,需要執行 photoshop 來進行自動化作業
然而要怎麼知道使用者安裝了哪一版的 photoshop?
而 applescript 也沒有好方法可以撈取 Application 下的清單
加上需要先行 complie,也就是透過動態變數的話
applescript 就不知道你想執行的程式有什麼樣的功能能夠執行
以下是知道 photoshop 應用程式名稱的程式碼

on run filePath
  tell application "Adobe Photoshop 2021"
    activate
    set UnixPath to POSIX path of ((path to me as text) & "::")
    open alias filePath
  end tell
end run

為了解決這個問題,我們同樣透過 Unity 端來幫助完成
首先,我們先抓取在 Application 下的 Photoshop 名稱
透過 Environment.SpecialFolder.ProgramFiles 對應到 MacOS 下的 Applications

var files = Directory.GetDirectories(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles));
var ps = string.Empty;
foreach (var file in files)
{
	if (file.Contains("Photoshop"))
	{
		ps = Path.GetFileName(file);
		break;
	}
}

接著,再把 applescript 複製一份變成 template
把 “Adobe Photoshop 2021” 換成 “$APP_NAME”
然後,只要將字串取代成抓到 Photoshop 名稱,寫入原本的 applescript 檔即可

on run filePath
  tell application "$APP_NAME"
    activate
    set UnixPath to POSIX path of ((path to me as text) & "::")
    open alias filePath
  end tell
end run

發佈留言