diff --git a/inst/private/cygpath.m b/inst/private/cygpath.m
new file mode 100644
index 000000000..5cc40b81d
--- /dev/null
+++ b/inst/private/cygpath.m
@@ -0,0 +1,40 @@
+%% Copyright (C) 2022 Alex Vong
+%%
+%% This file is part of OctSymPy.
+%%
+%% OctSymPy is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU General Public License as published
+%% by the Free Software Foundation; either version 3 of the License,
+%% or (at your option) any later version.
+%%
+%% This software is distributed in the hope that it will be useful,
+%% but WITHOUT ANY WARRANTY; without even the implied warranty
+%% of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+%% the GNU General Public License for more details.
+%%
+%% You should have received a copy of the GNU General Public
+%% License along with this software; see the file COPYING.
+%% If not, see .
+
+%% -*- texinfo -*-
+%% @defun cygpath ()
+%% Convert Windows native path to Cygwin POSIX-style path.
+%%
+%% @seealso{python_env_is_cygwin_like}
+%% @end defun
+
+function posix_path = cygpath (native_path)
+ %% FIXME: only allow safe characters inside "..."
+ if ~isempty (strfind (native_path, '"'))
+ error ('cygpath: native path %s must not contain "', native_path);
+ end
+
+ [status, out] = system (['cygpath -u "' native_path '"']);
+ if status ~= 0
+ error ('cygpath: cygpath exited with status %d', status);
+ end
+
+ %% validate output
+ assert (ischar (out) && logical (regexp (out, '^[^\r\n]+[\r]?[\n]$')));
+ posix_path = regexprep (out, '[\r]?[\n]$', ''); % strip trailing newline
+end
diff --git a/inst/private/python_env_is_cygwin_like.m b/inst/private/python_env_is_cygwin_like.m
new file mode 100644
index 000000000..7918ddde8
--- /dev/null
+++ b/inst/private/python_env_is_cygwin_like.m
@@ -0,0 +1,53 @@
+%% Copyright (C) 2022 Alex Vong
+%%
+%% This file is part of OctSymPy.
+%%
+%% OctSymPy is free software; you can redistribute it and/or modify
+%% it under the terms of the GNU General Public License as published
+%% by the Free Software Foundation; either version 3 of the License,
+%% or (at your option) any later version.
+%%
+%% This software is distributed in the hope that it will be useful,
+%% but WITHOUT ANY WARRANTY; without even the implied warranty
+%% of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
+%% the GNU General Public License for more details.
+%%
+%% You should have received a copy of the GNU General Public
+%% License along with this software; see the file COPYING.
+%% If not, see .
+
+%% -*- texinfo -*-
+%% @defun python_env_is_cygwin_like (pyexec)
+%% Check if Python @var{pyexec} is running in a Cygwin-like POSIX environment,
+%% such as Cygwin or MSYS2. The result is memoized to speed up subsequent
+%% calls.
+%%
+%% @seealso{cygpath}
+%% @end defun
+
+function r = python_env_is_cygwin_like (pyexec)
+ persistent python_env_is_cygwin_like_memo
+
+ if ~isempty (python_env_is_cygwin_like_memo)
+ r = python_env_is_cygwin_like_memo;
+ elseif ispc ()
+ if system ('where /q cygpath') ~= 0
+ r = false;
+ python_env_is_cygwin_like_memo = r;
+ return
+ end
+
+ [status, out] = system ([pyexec ' -c "import os; print(os.name)"']);
+ if status ~= 0
+ error ('python_env_is_cygwin_like: %s exited with status %d', ...
+ pyexec, status);
+ end
+ assert (ischar (out));
+
+ r = ~isempty (regexp(out, 'posix', 'match'));
+ python_env_is_cygwin_like_memo = r;
+ else
+ r = false;
+ python_env_is_cygwin_like_memo = r;
+ end
+end
diff --git a/inst/private/python_ipc_system.m b/inst/private/python_ipc_system.m
index c264f153b..7bb868045 100644
--- a/inst/private/python_ipc_system.m
+++ b/inst/private/python_ipc_system.m
@@ -1,4 +1,5 @@
%% Copyright (C) 2014-2016, 2022 Colin B. Macdonald
+%% Copyright (C) 2022 Alex Vong
%%
%% This file is part of OctSymPy.
%%
@@ -131,7 +132,13 @@
error ('system ipc: failed to close %s (fd %d) after writing', ...
tmpfilename, fd);
end
- [status, out] = system ([pyexec ' ' tmpfilename]);
+
+ if python_env_is_cygwin_like (pyexec)
+ converted_tmpfilename = cygpath (tmpfilename);
+ else
+ converted_tmpfilename = tmpfilename;
+ end
+ [status, out] = system ([pyexec ' ' converted_tmpfilename]);
end
info.raw = out;