Skip to content

Commit f54892a

Browse files
authored
Use Wine directly instead of wrappers on Mac and Linux (#1571)
* Add Unix executable file type. * Use Wine directly instead of wrappers * Exe installer do not create wrappers * Tell user to set wine path in exe setup message * Fix non-exe tools showing on windows
1 parent c88a308 commit f54892a

14 files changed

Lines changed: 114 additions & 115 deletions

OpenUtau.Core/Classic/ExeInstaller.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,6 @@ public static void Install(string filePath, ExeType exeType) {
1515
: PathManager.Inst.ResamplersPath;
1616
string destName = Path.Combine(destPath, fileName);
1717
File.Copy(filePath, destName, true);
18-
19-
if (OS.IsMacOS()) {
20-
//reference: https://github.com/stakira/OpenUtau/wiki/Resamplers-and-Wavtools#macos
21-
string MacWrapper = $"#!/bin/sh\r\nRELPATH=\"{fileName}\"\r\n\r\nABSPATH=$(cd \"$(dirname \"$0\")\"; pwd -P)\r\nABSPATH=\"$ABSPATH/$RELPATH\"\r\nif [[ ! -x \"$ABSPATH\" ]]\r\nthen\r\n chmod +x \"$ABSPATH\"\r\nfi\r\nexec /usr/local/bin/wine32on64 \"$ABSPATH\" \"$@\"";
22-
File.WriteAllText(Path.ChangeExtension(destName, ".sh"), MacWrapper, new UTF8Encoding(false));
23-
} else if (OS.IsLinux()) {
24-
//reference: https://github.com/stakira/OpenUtau/wiki/Resamplers-and-Wavtools#linux
25-
string LinuxWrapper = $"#!/bin/bash\r\nLANG=\"ja_JP.UTF8\" wine \"{destName}\" \"${{@,-1}}\"";
26-
File.WriteAllText(Path.ChangeExtension(destName, null), LinuxWrapper, new UTF8Encoding(false));
27-
}
2818

2919
new Task(() => {
3020
DocManager.Inst.ExecuteCmd(new SingersChangedNotification());

OpenUtau.Core/Classic/ExeResampler.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ internal class ExeResampler : IResampler {
2020
public ResamplerManifest Manifest { get; private set; }
2121
readonly string _name;
2222
readonly bool _isLegalPlugin = false;
23+
readonly string winePath;
24+
readonly bool useWine;
2325

2426

2527
public ResamplerManifest LoadManifest() {
@@ -63,6 +65,10 @@ public ExeResampler(string filePath, string basePath) {
6365
_name = Path.GetRelativePath(basePath, filePath);
6466
_isLegalPlugin = true;
6567
}
68+
//Check if should use wine
69+
string ext = Path.GetExtension(filePath).ToLower();
70+
winePath = Preferences.Default.WinePath;
71+
useWine = !OS.IsWindows() && !string.IsNullOrEmpty(winePath) && (ext == ".exe" || ext == ".bat");
6672
//Load Resampler Manifest
6773
Manifest = LoadManifest();
6874
//Make moresampler happy
@@ -96,7 +102,11 @@ public string DoResamplerReturnsFile(ResamplerItem args, ILogger logger) {
96102
string ArgParam = FormattableString.Invariant(
97103
$"\"{args.inputTemp}\" \"{tmpFile}\" {MusicMath.GetToneName(args.tone)} {args.velocity} \"{args.GetFlagsString()}\" {args.offset} {args.durRequired} {args.consonant} {args.cutoff} {args.volume} {args.modulation} !{args.tempo} {Base64.Base64EncodeInt12(args.pitches)}");
98104
logger.Information($" > [thread-{threadId}] {FilePath} {ArgParam}");
99-
ProcessRunner.Run(FilePath, ArgParam, logger);
105+
if (useWine) {
106+
ProcessRunner.Run(winePath, $"{FilePath} {ArgParam}", logger);
107+
} else {
108+
ProcessRunner.Run(FilePath, ArgParam, logger);
109+
}
100110
return tmpFile;
101111
}
102112

OpenUtau.Core/Classic/ExeWavtool.cs

Lines changed: 14 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,21 @@
1414
namespace OpenUtau.Classic {
1515
class ExeWavtool : IWavtool {
1616
static object tempBatLock = new object();
17-
static object tempShLock = new object();
1817

1918
readonly StringBuilder sb = new StringBuilder();
2019
readonly string filePath;
2120
readonly string name;
21+
readonly string winePath;
22+
readonly bool useWine;
2223
private Encoding osEncoding;
2324

2425
public ExeWavtool(string filePath, string basePath) {
2526
this.filePath = filePath;
2627
name = Path.GetRelativePath(basePath, filePath);
2728
osEncoding = OS.IsWindows() ? Encoding.GetEncoding(0) : Encoding.UTF8;
29+
string ext = Path.GetExtension(filePath).ToLower();
30+
winePath = Preferences.Default.WinePath;
31+
useWine = !OS.IsWindows() && !string.IsNullOrEmpty(winePath) && (ext == ".exe" || ext == ".bat");
2832
}
2933

3034
public float[] Concatenate(List<ResamplerItem> resamplerItems, string tempPath, CancellationTokenSource cancellation) {
@@ -45,7 +49,7 @@ public float[] Concatenate(List<ResamplerItem> resamplerItems, string tempPath,
4549
lock (tempBatLock) {
4650
using (var stream = File.Open(batPath, FileMode.Create)) {
4751
UTF8Encoding noBomEncoding = new UTF8Encoding(false);
48-
using (var writer = new StreamWriter(stream, OS.IsLinux() ? noBomEncoding : osEncoding)) {
52+
using (var writer = new StreamWriter(stream, OS.IsWindows() ? osEncoding : noBomEncoding)) {
4953
WriteSetUp(writer, resamplerItems, tempPath);
5054
for (var i = 0; i < resamplerItems.Count; i++) {
5155
WriteItem(writer, resamplerItems[i], i, resamplerItems.Count);
@@ -54,14 +58,12 @@ public float[] Concatenate(List<ResamplerItem> resamplerItems, string tempPath,
5458
}
5559
}
5660

57-
if (OS.IsLinux()) {
58-
//Because you can't run .bat files directly on linux, we have to create a shell script wrapper
59-
string shPath = PrepareSh();
60-
ProcessRunner.Run(shPath, "", Log.Logger, workDir: PathManager.Inst.CachePath, timeoutMs: 5 * 60 * 1000);
61-
}
62-
else {
61+
if (useWine) {
62+
ProcessRunner.Run(winePath, batPath, Log.Logger, workDir: PathManager.Inst.CachePath, timeoutMs: 5 * 60 * 1000);
63+
} else {
6364
ProcessRunner.Run(batPath, "", Log.Logger, workDir: PathManager.Inst.CachePath, timeoutMs: 5 * 60 * 1000);
6465
}
66+
6567
}
6668
if (string.IsNullOrEmpty(tempPath) || File.Exists(tempPath)) {
6769
using (var wavStream = Core.Format.Wave.OpenFile(tempPath)) {
@@ -71,39 +73,14 @@ public float[] Concatenate(List<ResamplerItem> resamplerItems, string tempPath,
7173
return new float[0];
7274
}
7375

74-
string PrepareSh () {
75-
string shPath = Path.Join(PathManager.Inst.CachePath, "temp.sh");
76-
lock(tempShLock) {
77-
if (!File.Exists(shPath)) {
78-
using (FileStream stream = File.Open(shPath, FileMode.Create)) {
79-
//Making a new encoding here that does not have a byte order mark
80-
//The byte order mark at the front of an shell script causes an exec format error
81-
UTF8Encoding noBomEncoding = new UTF8Encoding(false);
82-
using (StreamWriter writer = new StreamWriter(stream, noBomEncoding)) {
83-
WriteSh(writer);
84-
}
85-
}
86-
int mode = (7 << 6) | (5 << 3) | 5;
87-
chmod(shPath, mode);
88-
}
89-
}
90-
return shPath;
91-
}
92-
93-
void WriteSh (StreamWriter writer) {
94-
string batPath = Path.Combine(PathManager.Inst.CachePath, "temp.bat");
95-
writer.WriteLine("#!/bin/bash");
96-
writer.WriteLine("LANG=\"ja_JP.UTF8\" wine \"" + batPath + "\" \"${@,-1}\"");
97-
}
98-
9976
void PrepareHelper() {
10077
string tempHelper = Path.Join(PathManager.Inst.CachePath, "temp_helper.bat");
10178
lock (Renderers.GetCacheLock(tempHelper)) {
10279
if (!File.Exists(tempHelper)) {
10380
using (var stream = File.Open(tempHelper, FileMode.Create)) {
10481
//BOM also causes problems when running .bat files through wine
10582
UTF8Encoding noBomEncoding = new UTF8Encoding(false);
106-
using (var writer = new StreamWriter(stream, OS.IsLinux() ? noBomEncoding : osEncoding)) {
83+
using (var writer = new StreamWriter(stream, OS.IsWindows() ? osEncoding : noBomEncoding)) {
10784
WriteHelper(writer);
10885
}
10986
}
@@ -127,8 +104,7 @@ void WriteSetUp(StreamWriter writer, List<ResamplerItem> resamplerItems, string
127104
writer.WriteLine($"@set tempo={resamplerItems[0].tempo}");
128105
writer.WriteLine($"@set samples={44100}");
129106
writer.WriteLine($"@set oto={ConvertIfNeeded(PathManager.Inst.CachePath)}");
130-
string toolPath = OS.IsLinux() ? ResolveResamplerExePathLinux(filePath) : filePath;
131-
writer.WriteLine($"@set tool={ConvertIfNeeded(toolPath)}");
107+
writer.WriteLine($"@set tool={ConvertIfNeeded(filePath)}");
132108
string tempFile = Path.GetRelativePath(PathManager.Inst.CachePath, tempPath);
133109
writer.WriteLine($"@set output={ConvertIfNeeded(tempFile)}");
134110
writer.WriteLine("@set helper=temp_helper.bat");
@@ -143,8 +119,7 @@ void WriteSetUp(StreamWriter writer, List<ResamplerItem> resamplerItems, string
143119
}
144120

145121
void WriteItem(StreamWriter writer, ResamplerItem item, int index, int total) {
146-
string resampPath = OS.IsLinux() ? ResolveResamplerExePathLinux(item.resampler.FilePath) : item.resampler.FilePath;
147-
writer.WriteLine($"@set resamp={ConvertIfNeeded(resampPath)}");
122+
writer.WriteLine($"@set resamp={ConvertIfNeeded(item.resampler.FilePath)}");
148123
writer.WriteLine($"@set params={item.volume} {item.modulation} !{item.tempo:G999} {Base64.Base64EncodeInt12(item.pitches)}");
149124
// fixed the commandline vulnerabilities that also exists in og utau
150125
writer.WriteLine($"@set flag=\"{EscapeFlags(item.GetFlagsString())}\"");
@@ -221,7 +196,7 @@ void WriteTearDown(StreamWriter writer) {
221196
}
222197

223198
string ConvertIfNeeded(string path) {
224-
if (OS.IsLinux()) return ConvertToWindowsPath(path);
199+
if (!OS.IsWindows()) return ConvertToWindowsPath(path);
225200
else return path;
226201
}
227202

@@ -243,46 +218,6 @@ string ConvertToWindowsPath (string linuxPath) {
243218
return windowsPath;
244219
}
245220

246-
//Parse the wrapper shell script created by the user during the resampler install process on linux for the path
247-
//to the resampler's exe file. Should work for most paths and files that people would make, but there may be edge cases.
248-
//Intended only for use on linux
249-
string ResolveResamplerExePathLinux (string wrapperPath) {
250-
using (FileStream stream = File.Open(wrapperPath, FileMode.Open)) {
251-
using (StreamReader reader = new StreamReader(stream)) {
252-
string line;
253-
int start = -1;
254-
int end = -1;
255-
for (line = reader.ReadLine(); line != null; line = reader.ReadLine()) {
256-
if (line[0] == '#') continue; //ignore comments in the file
257-
start = line.IndexOf("wine ") + 5;
258-
if (start == -1) continue;
259-
260-
end = -1;
261-
if (line[start] == '"') { //if path is enclosed by quotation marks
262-
start++;
263-
end = line.IndexOf('"', start + 1);
264-
}
265-
else { //if path is not enclosed by quotation marks (potential for "\ " in string)
266-
int lastChecked = start;
267-
do {
268-
end = line.IndexOf(' ', lastChecked);
269-
if (line[end - 1] != '\\') break;
270-
271-
lastChecked = end + 1;
272-
} while (end != -1);
273-
}
274-
if (end != -1) break;
275-
}
276-
if (line == null)
277-
throw new InvalidDataException("Shell script wrapper for exe resampler is empty");
278-
else if (start == -1 || end == -1)
279-
throw new InvalidDataException("Could not find path to .exe resampler in shell script wrapper");
280-
else
281-
return line.Substring(start, end - start);
282-
}
283-
}
284-
}
285-
286221
[DllImport("libc", SetLastError = true)]
287222
private static extern int chmod(string pathname, int mode);
288223

OpenUtau.Core/Classic/Plugin.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Diagnostics;
22
using System.IO;
3+
using OpenUtau.Core.Util;
34

45
namespace OpenUtau.Classic {
56
public class Plugin : IPlugin {
@@ -16,9 +17,12 @@ public void Run(string tempFile) {
1617
if (!File.Exists(Executable)) {
1718
throw new FileNotFoundException($"Executable {Executable} not found.");
1819
}
20+
string winePath = Preferences.Default.WinePath;
21+
bool useWine = !OS.IsWindows() && !string.IsNullOrEmpty(winePath);
1922
var startInfo = new ProcessStartInfo() {
20-
FileName = Executable,
21-
Arguments = $"\"{tempFile}\"",
23+
FileName = useWine ? winePath : Executable,
24+
Arguments = useWine ? $"\"{Executable}\" \"{tempFile}\"" : $"\"{tempFile}\"",
25+
Environment = {{"LANG", "ja_JP.utf8"}},
2226
WorkingDirectory = Path.GetDirectoryName(Executable),
2327
UseShellExecute = UseShell,
2428
};

OpenUtau.Core/Classic/ToolsManager.cs

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,11 @@ IResampler LoadResampler(string filePath, string basePath) {
3838
return null;
3939
}
4040
string ext = Path.GetExtension(filePath).ToLower();
41-
if (OS.IsWindows()) {
42-
if (ext == ".exe" || ext == ".bat") {
43-
return new ExeResampler(filePath, basePath);
44-
}
45-
} else {
46-
if (ext == ".sh" || string.IsNullOrEmpty(ext)) {
47-
return new ExeResampler(filePath, basePath);
48-
}
41+
if ((OS.IsWindows() || !string.IsNullOrEmpty(Preferences.Default.WinePath)) && (ext == ".exe" || ext == ".bat")) {
42+
return new ExeResampler(filePath, basePath);
43+
}
44+
if (!OS.IsWindows() && (ext == ".sh" || string.IsNullOrEmpty(ext))) {
45+
return new ExeResampler(filePath, basePath);
4946
}
5047
return null;
5148
}
@@ -55,14 +52,11 @@ IWavtool LoadWavtool(string filePath, string basePath) {
5552
return null;
5653
}
5754
string ext = Path.GetExtension(filePath).ToLower();
58-
if (OS.IsWindows()) {
59-
if (ext == ".exe" || ext == ".bat") {
60-
return new ExeWavtool(filePath, basePath);
61-
}
62-
} else {
63-
if (ext == ".sh" || string.IsNullOrEmpty(ext)) {
64-
return new ExeWavtool(filePath, basePath);
65-
}
55+
if ((OS.IsWindows() || !string.IsNullOrEmpty(Preferences.Default.WinePath)) && (ext == ".exe" || ext == ".bat")) {
56+
return new ExeWavtool(filePath, basePath);
57+
}
58+
if (!OS.IsWindows() && (ext == ".sh" || string.IsNullOrEmpty(ext))) {
59+
return new ExeWavtool(filePath, basePath);
6660
}
6761
return null;
6862
}

OpenUtau.Core/Util/Preferences.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ public class SerializablePreferences {
188188
public bool RememberMid = false;
189189
public bool RememberUst = true;
190190
public bool RememberVsqx = true;
191+
public string WinePath = string.Empty;
191192
public string PhoneticAssistant = string.Empty;
192193
public string RecentOpenSingerDirectory = string.Empty;
193194
public string RecentOpenProjectDirectory = string.Empty;

OpenUtau.Core/Util/ProcessRunner.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public static void Run(string file, string args, ILogger logger, string workDir
1414
var threadId = Thread.CurrentThread.ManagedThreadId;
1515
using (var proc = new Process()) {
1616
proc.StartInfo = new ProcessStartInfo(file, args) {
17+
Environment = {{"LANG", "ja_JP.utf8"}},
1718
UseShellExecute = false,
1819
RedirectStandardOutput = DebugSwitch,
1920
RedirectStandardError = true,

OpenUtau/FilePicker.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ internal class FilePicker {
5656
public static FilePickerFileType OUDEP { get; } = new("OpenUtau dependency") {
5757
Patterns = new[] { "*.oudep" },
5858
};
59+
public static FilePickerFileType UnixExecutable { get; } = new("Executable") {
60+
MimeTypes = new[] { "application/x-executable" },
61+
AppleUniformTypeIdentifiers = new[] { "public.unix-executable" },
62+
};
5963

6064
public async static Task<string?> OpenFile(
6165
Window window, string titleKey, params FilePickerFileType[] types) {

OpenUtau/Strings/Strings.axaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ Warning: this option removes custom presets.</system:String>
379379
<system:String x:Key="prefs.advanced.resamplerlogging.warn">Stores resampler output in log files. This option slows down UI and rendering.</system:String>
380380
<system:String x:Key="prefs.advanced.stable">Stable</system:String>
381381
<system:String x:Key="prefs.advanced.vlabelerpath">vLabeler Path</system:String>
382+
<system:String x:Key="prefs.advanced.winepath">Wine Path (set to enable wine for compatibility)</system:String>
382383
<system:String x:Key="prefs.appearance">Appearance</system:String>
383384
<system:String x:Key="prefs.appearance.degree">Scale degree display style</system:String>
384385
<system:String x:Key="prefs.appearance.degree.numbered">Numbered (1 2 3 4 5 6 7)</system:String>
@@ -412,6 +413,7 @@ Warning: this option removes custom presets.</system:String>
412413
<system:String x:Key="prefs.paths.loaddeepfolders">Load all depth folders</system:String>
413414
<system:String x:Key="prefs.paths.reset">Reset</system:String>
414415
<system:String x:Key="prefs.paths.select">Select</system:String>
416+
<system:String x:Key="prefs.paths.detect">Detect</system:String>
415417
<system:String x:Key="prefs.penplus">Set Pen Plus Tool as Default</system:String>
416418
<system:String x:Key="prefs.playback">Playback</system:String>
417419
<system:String x:Key="prefs.playback.autoscroll">Auto-Scroll</system:String>

OpenUtau/ViewModels/ExeSetupViewModel.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ public class ExeSetupViewModel : ViewModelBase {
66
[Reactive] public string message { get; set; }
77
public ExeSetupViewModel(string filePath) {
88
this.filePath = filePath;
9-
message = "installing " + filePath;
9+
message = $"Installing {filePath}...\n\n";
1010
if (OS.IsMacOS()) {
11-
message += "To use exe resamplers or wavtools on MacOS, please install wine32on64 using following commands:\n"
12-
+ "brew tap gcenx/wine\n"
13-
+ "brew install --cask --no-quarantine wine-crossover";
14-
}else if(OS.IsLinux()) {
15-
message += "To use exe resamplers or wavtools on Linux, please install wine from https://www.winehq.org/";
11+
message += "To use exe resamplers or wavtools on MacOS:\n"
12+
+ "1. Install wine using following commands:\n"
13+
+ " % brew tap gcenx/wine\n"
14+
+ " % brew install --cask --no-quarantine wine-crossover\n"
15+
+ "2. Set wine path in Preferences > Advanced > Wine Path";
16+
} else if(OS.IsLinux()) {
17+
message += "To use exe resamplers or wavtools on Linux:\n"
18+
+ "1. Install wine from https://www.winehq.org/\n"
19+
+ "2. Set wine path in Preferences > Advanced > Wine Path";
1620
}
1721
}
1822
}

0 commit comments

Comments
 (0)