summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Dower <steve.dower@python.org>2021-04-23 17:03:17 (GMT)
committerGitHub <noreply@github.com>2021-04-23 17:03:17 (GMT)
commit019e9e816882f5c43c4b833f81844b8299e815fd (patch)
treeadb876fd609653dbaa6f9dfb76c9da68190957a2
parent3513d55a617012002c3f82dbf3cec7ec1abd7090 (diff)
downloadcpython-019e9e816882f5c43c4b833f81844b8299e815fd.zip
cpython-019e9e816882f5c43c4b833f81844b8299e815fd.tar.gz
cpython-019e9e816882f5c43c4b833f81844b8299e815fd.tar.bz2
bpo-43538: Add extra arguments to os.startfile (GH-25538)
-rw-r--r--Doc/library/os.rst28
-rw-r--r--Lib/test/test_startfile.py10
-rw-r--r--Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-43538.F0Cg6X.rst (renamed from Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-35306.F0Cg6X.rst)0
-rw-r--r--Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst1
-rw-r--r--Modules/clinic/posixmodule.c.h87
-rw-r--r--Modules/posixmodule.c25
6 files changed, 126 insertions, 25 deletions
diff --git a/Doc/library/os.rst b/Doc/library/os.rst
index 371d59e..41ef50d 100644
--- a/Doc/library/os.rst
+++ b/Doc/library/os.rst
@@ -4155,7 +4155,7 @@ written in Python, such as a mail server's external command delivery program.
.. availability:: Windows.
-.. function:: startfile(path[, operation])
+.. function:: startfile(path, [operation], [arguments], [cwd], [show_cmd])
Start a file with its associated application.
@@ -4169,13 +4169,25 @@ written in Python, such as a mail server's external command delivery program.
``'print'`` and ``'edit'`` (to be used on files) as well as ``'explore'`` and
``'find'`` (to be used on directories).
+ When launching an application, specify *arguments* to be passed as a single
+ string. This argument may have no effect when using this function to launch a
+ document.
+
+ The default working directory is inherited, but may be overridden by the *cwd*
+ argument. This should be an absolute path. A relative *path* will be resolved
+ against this argument.
+
+ Use *show_cmd* to override the default window style. Whether this has any
+ effect will depend on the application being launched. Values are integers as
+ supported by the Win32 :c:func:`ShellExecute` function.
+
:func:`startfile` returns as soon as the associated application is launched.
There is no option to wait for the application to close, and no way to retrieve
the application's exit status. The *path* parameter is relative to the current
- directory. If you want to use an absolute path, make sure the first character
- is not a slash (``'/'``); the underlying Win32 :c:func:`ShellExecute` function
- doesn't work if it is. Use the :func:`os.path.normpath` function to ensure that
- the path is properly encoded for Win32.
+ directory or *cwd*. If you want to use an absolute path, make sure the first
+ character is not a slash (``'/'``) Use :mod:`pathlib` or the
+ :func:`os.path.normpath` function to ensure that paths are properly encoded for
+ Win32.
To reduce interpreter startup overhead, the Win32 :c:func:`ShellExecute`
function is not resolved until this function is first called. If the function
@@ -4183,8 +4195,14 @@ written in Python, such as a mail server's external command delivery program.
.. audit-event:: os.startfile path,operation os.startfile
+ .. audit-event:: os.startfile/2 path,operation,arguments,cwd,show_cmd os.startfile
+
.. availability:: Windows.
+ .. versionchanged:: 3.10
+ Added the *arguments*, *cwd* and *show_cmd* arguments, and the
+ ``os.startfile/2`` audit event.
+
.. function:: system(command)
diff --git a/Lib/test/test_startfile.py b/Lib/test/test_startfile.py
index 589ffa2..8c64299 100644
--- a/Lib/test/test_startfile.py
+++ b/Lib/test/test_startfile.py
@@ -18,11 +18,11 @@ from os import path
startfile = support.get_attribute(os, 'startfile')
+@unittest.skipIf(platform.win32_is_iot(), "starting files is not supported on Windows IoT Core or nanoserver")
class TestCase(unittest.TestCase):
def test_nonexisting(self):
self.assertRaises(OSError, startfile, "nonexisting.vbs")
- @unittest.skipIf(platform.win32_is_iot(), "starting files is not supported on Windows IoT Core or nanoserver")
def test_empty(self):
# We need to make sure the child process starts in a directory
# we're not about to delete. If we're running under -j, that
@@ -32,6 +32,14 @@ class TestCase(unittest.TestCase):
empty = path.join(path.dirname(__file__), "empty.vbs")
startfile(empty)
startfile(empty, "open")
+ startfile(empty, cwd=path.dirname(sys.executable))
+
+ def test_python(self):
+ # Passing "-V" ensures that it closes quickly, though still not
+ # quickly enough that we can run in the test directory
+ cwd, name = path.split(sys.executable)
+ startfile(name, arguments="-V", cwd=cwd)
+ startfile(name, arguments="-V", cwd=cwd, show_cmd=0)
if __name__ == "__main__":
unittest.main()
diff --git a/Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-35306.F0Cg6X.rst b/Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-43538.F0Cg6X.rst
index af41b3c..af41b3c 100644
--- a/Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-35306.F0Cg6X.rst
+++ b/Misc/NEWS.d/next/Windows/2021-04-22-20-39-49.bpo-43538.F0Cg6X.rst
diff --git a/Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst b/Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst
new file mode 100644
index 0000000..f1ee2de
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2021-04-22-21-37-41.bpo-35306.10kSR-.rst
@@ -0,0 +1 @@
+Adds additional arguments to :func:`os.startfile` function.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 36bb7c3..7921c22 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -7125,7 +7125,8 @@ os_abort(PyObject *module, PyObject *Py_UNUSED(ignored))
#if defined(MS_WINDOWS)
PyDoc_STRVAR(os_startfile__doc__,
-"startfile($module, /, filepath, operation=<unrepresentable>)\n"
+"startfile($module, /, filepath, operation=<unrepresentable>,\n"
+" arguments=<unrepresentable>, cwd=None, show_cmd=1)\n"
"--\n"
"\n"
"Start a file with its associated application.\n"
@@ -7137,6 +7138,16 @@ PyDoc_STRVAR(os_startfile__doc__,
"When another \"operation\" is given, it specifies what should be done with\n"
"the file. A typical operation is \"print\".\n"
"\n"
+"\"arguments\" is passed to the application, but should be omitted if the\n"
+"file is a document.\n"
+"\n"
+"\"cwd\" is the working directory for the operation. If \"filepath\" is\n"
+"relative, it will be resolved against this directory. This argument\n"
+"should usually be an absolute path.\n"
+"\n"
+"\"show_cmd\" can be used to override the recommended visibility option.\n"
+"See the Windows ShellExecute documentation for values.\n"
+"\n"
"startfile returns as soon as the associated application is launched.\n"
"There is no option to wait for the application to close, and no way\n"
"to retrieve the application\'s exit status.\n"
@@ -7150,20 +7161,24 @@ PyDoc_STRVAR(os_startfile__doc__,
static PyObject *
os_startfile_impl(PyObject *module, path_t *filepath,
- const Py_UNICODE *operation);
+ const Py_UNICODE *operation, const Py_UNICODE *arguments,
+ path_t *cwd, int show_cmd);
static PyObject *
os_startfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
- static const char * const _keywords[] = {"filepath", "operation", NULL};
+ static const char * const _keywords[] = {"filepath", "operation", "arguments", "cwd", "show_cmd", NULL};
static _PyArg_Parser _parser = {NULL, _keywords, "startfile", 0};
- PyObject *argsbuf[2];
+ PyObject *argsbuf[5];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
path_t filepath = PATH_T_INITIALIZE("startfile", "filepath", 0, 0);
const Py_UNICODE *operation = NULL;
+ const Py_UNICODE *arguments = NULL;
+ path_t cwd = PATH_T_INITIALIZE("startfile", "cwd", 1, 0);
+ int show_cmd = 1;
- args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 2, 0, argsbuf);
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 5, 0, argsbuf);
if (!args) {
goto exit;
}
@@ -7173,20 +7188,54 @@ os_startfile(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
if (!noptargs) {
goto skip_optional_pos;
}
- if (!PyUnicode_Check(args[1])) {
- _PyArg_BadArgument("startfile", "argument 'operation'", "str", args[1]);
- goto exit;
+ if (args[1]) {
+ if (!PyUnicode_Check(args[1])) {
+ _PyArg_BadArgument("startfile", "argument 'operation'", "str", args[1]);
+ goto exit;
+ }
+ #if USE_UNICODE_WCHAR_CACHE
+ operation = _PyUnicode_AsUnicode(args[1]);
+ #else /* USE_UNICODE_WCHAR_CACHE */
+ operation = PyUnicode_AsWideCharString(args[1], NULL);
+ #endif /* USE_UNICODE_WCHAR_CACHE */
+ if (operation == NULL) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
}
- #if USE_UNICODE_WCHAR_CACHE
- operation = _PyUnicode_AsUnicode(args[1]);
- #else /* USE_UNICODE_WCHAR_CACHE */
- operation = PyUnicode_AsWideCharString(args[1], NULL);
- #endif /* USE_UNICODE_WCHAR_CACHE */
- if (operation == NULL) {
+ if (args[2]) {
+ if (!PyUnicode_Check(args[2])) {
+ _PyArg_BadArgument("startfile", "argument 'arguments'", "str", args[2]);
+ goto exit;
+ }
+ #if USE_UNICODE_WCHAR_CACHE
+ arguments = _PyUnicode_AsUnicode(args[2]);
+ #else /* USE_UNICODE_WCHAR_CACHE */
+ arguments = PyUnicode_AsWideCharString(args[2], NULL);
+ #endif /* USE_UNICODE_WCHAR_CACHE */
+ if (arguments == NULL) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ if (args[3]) {
+ if (!path_converter(args[3], &cwd)) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+ show_cmd = _PyLong_AsInt(args[4]);
+ if (show_cmd == -1 && PyErr_Occurred()) {
goto exit;
}
skip_optional_pos:
- return_value = os_startfile_impl(module, &filepath, operation);
+ return_value = os_startfile_impl(module, &filepath, operation, arguments, &cwd, show_cmd);
exit:
/* Cleanup for filepath */
@@ -7195,6 +7244,12 @@ exit:
#if !USE_UNICODE_WCHAR_CACHE
PyMem_Free((void *)operation);
#endif /* USE_UNICODE_WCHAR_CACHE */
+ /* Cleanup for arguments */
+ #if !USE_UNICODE_WCHAR_CACHE
+ PyMem_Free((void *)arguments);
+ #endif /* USE_UNICODE_WCHAR_CACHE */
+ /* Cleanup for cwd */
+ path_cleanup(&cwd);
return return_value;
}
@@ -9208,4 +9263,4 @@ exit:
#ifndef OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#define OS_WAITSTATUS_TO_EXITCODE_METHODDEF
#endif /* !defined(OS_WAITSTATUS_TO_EXITCODE_METHODDEF) */
-/*[clinic end generated code: output=ede310b1d316d2b2 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=65a85d7d3f2c487e input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 85e1e69..e754db7 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -12485,6 +12485,9 @@ check_ShellExecute()
os.startfile
filepath: path_t
operation: Py_UNICODE = NULL
+ arguments: Py_UNICODE = NULL
+ cwd: path_t(nullable=True) = None
+ show_cmd: int = 1
Start a file with its associated application.
@@ -12495,6 +12498,16 @@ application (if any) its extension is associated.
When another "operation" is given, it specifies what should be done with
the file. A typical operation is "print".
+"arguments" is passed to the application, but should be omitted if the
+file is a document.
+
+"cwd" is the working directory for the operation. If "filepath" is
+relative, it will be resolved against this directory. This argument
+should usually be an absolute path.
+
+"show_cmd" can be used to override the recommended visibility option.
+See the Windows ShellExecute documentation for values.
+
startfile returns as soon as the associated application is launched.
There is no option to wait for the application to close, and no way
to retrieve the application's exit status.
@@ -12506,8 +12519,9 @@ the underlying Win32 ShellExecute function doesn't work if it is.
static PyObject *
os_startfile_impl(PyObject *module, path_t *filepath,
- const Py_UNICODE *operation)
-/*[clinic end generated code: output=66dc311c94d50797 input=c940888a5390f039]*/
+ const Py_UNICODE *operation, const Py_UNICODE *arguments,
+ path_t *cwd, int show_cmd)
+/*[clinic end generated code: output=3baa4f9795841880 input=8248997b80669622]*/
{
HINSTANCE rc;
@@ -12521,10 +12535,15 @@ os_startfile_impl(PyObject *module, path_t *filepath,
if (PySys_Audit("os.startfile", "Ou", filepath->object, operation) < 0) {
return NULL;
}
+ if (PySys_Audit("os.startfile/2", "OuuOi", filepath->object, operation,
+ arguments, cwd->object ? cwd->object : Py_None,
+ show_cmd) < 0) {
+ return NULL;
+ }
Py_BEGIN_ALLOW_THREADS
rc = Py_ShellExecuteW((HWND)0, operation, filepath->wide,
- NULL, NULL, SW_SHOWNORMAL);
+ arguments, cwd->wide, show_cmd);
Py_END_ALLOW_THREADS
if (rc <= (HINSTANCE)32) {