diff options
author | Steve Dower <steve.dower@python.org> | 2021-04-23 17:03:17 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-04-23 17:03:17 (GMT) |
commit | 019e9e816882f5c43c4b833f81844b8299e815fd (patch) | |
tree | adb876fd609653dbaa6f9dfb76c9da68190957a2 | |
parent | 3513d55a617012002c3f82dbf3cec7ec1abd7090 (diff) | |
download | cpython-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.rst | 28 | ||||
-rw-r--r-- | Lib/test/test_startfile.py | 10 | ||||
-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-.rst | 1 | ||||
-rw-r--r-- | Modules/clinic/posixmodule.c.h | 87 | ||||
-rw-r--r-- | Modules/posixmodule.c | 25 |
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) { |