From 6f19a5cbc0c0410e2bea71f5e691c6a3a300e0ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 16 Oct 2020 13:29:50 +0100 Subject: [PATCH 1/3] Add ps_switch_to() Windows only. Send alert to the window of a process. --- NAMESPACE | 1 + R/windows.R | 60 +++++++++++++++++++++++++++++++++++++++++++ man/ps_switch_to.Rd | 50 ++++++++++++++++++++++++++++++++++++ src/api-windows.c | 62 +++++++++++++++++++++++++++++++++++++++++++++ src/dummy.c | 1 + src/init.c | 1 + src/ps.h | 1 + 7 files changed, 176 insertions(+) create mode 100644 R/windows.R create mode 100644 man/ps_switch_to.Rd diff --git a/NAMESPACE b/NAMESPACE index 604d673e..84dabecd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -48,6 +48,7 @@ export(ps_shared_lib_users) export(ps_shared_libs) export(ps_status) export(ps_suspend) +export(ps_switch_to) export(ps_system_memory) export(ps_system_swap) export(ps_terminal) diff --git a/R/windows.R b/R/windows.R new file mode 100644 index 00000000..fcaefa66 --- /dev/null +++ b/R/windows.R @@ -0,0 +1,60 @@ + +#' Send an alert to the window of a process +#' +#' This function currently only works on Windows and errors on +#' other platforms. +#' +#' R processes might not have an associated window, e.g. if they are +#' running in a terminal, or in RStudio. In RGui they do. +#' +#' @param p Process handle. +#' @param ancestors Logical flag. Whether to try to alert the +#' closest ancestor in the process tree, if the specified process +#' does not have an associated window. It uses [ps_descent()] to +#' look up the ancestors. +#' @return A named list: +#' * `proc`: a process handle. This is the handle that ps tried to +#' alert. It might that same as `p` or an ancestor, if +#' `ansectors = TRUE` was specified. This is `NULL` if ps did +#' not find any process with an associated Window to alert. +#' * `success`: whether the Windows API call returned success. +#' If this is `TRUE` that typically means that the alerted +#' window is the active one. If it is `FALSE`, then the alert was +#' probably still sent, and the user will see it on the status bar +#' (in Windows 10). +#' +#' @export +#' @examples +#' \dontrun{ +#' # This usually does nothing interactively, since the current +#' # (RStudio, RGui, Windows Terminal, etc.) window is on top. +#' ps_switch_to() +#' +#' # Try switching to another window, while the sleep is running, +#' # and then you'll see an alert for the current +#' # (RStudio, RGui, etc.) window +#' Sys.sleep(4); ps_switch_to() +#' } + +ps_switch_to <- function(p = ps_handle(), ancestors = TRUE) { + os <- ps_os_type() + if (!os[["WINDOWS"]]) { + stop("`ps_windows()` currently only works on Windows") + } + assert_ps_handle(p) + + if (ancestors) { + plist <- ps_descent(p) + } else { + plist <- list(p) + } + pids <- map_int(plist, ps_pid) + + ret <- .Call(psll_switch_to, pids) + if (ret == 0) { + list(proc = NULL, success = FALSE) + } else { + # -pid on failure + list(proc = plist[[match(abs(ret), pids)]], success = ret > 0) + } +} diff --git a/man/ps_switch_to.Rd b/man/ps_switch_to.Rd new file mode 100644 index 00000000..b3ef77f5 --- /dev/null +++ b/man/ps_switch_to.Rd @@ -0,0 +1,50 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/windows.R +\name{ps_switch_to} +\alias{ps_switch_to} +\title{Send an alert to the window of a process} +\usage{ +ps_switch_to(p = ps_handle(), ancestors = TRUE) +} +\arguments{ +\item{p}{Process handle.} + +\item{ancestors}{Logical flag. Whether to try to alert the +closest ancestor in the process tree, if the specified process +does not have an associated window. It uses \code{\link[=ps_descent]{ps_descent()}} to +look up the ancestors.} +} +\value{ +A named list: +\itemize{ +\item \code{proc}: a process handle. This is the handle that ps tried to +alert. It might that same as \code{p} or an ancestor, if +\code{ansectors = TRUE} was specified. This is \code{NULL} if ps did +not find any process with an associated Window to alert. +\item \code{success}: whether the Windows API call returned success. +If this is \code{TRUE} that typically means that the alerted +window is the active one. If it is \code{FALSE}, then the alert was +probably still sent, and the user will see it on the status bar +(in Windows 10). +} +} +\description{ +This function currently only works on Windows and errors on +other platforms. +} +\details{ +R processes might not have an associated window, e.g. if they are +running in a terminal, or in RStudio. In RGui they do. +} +\examples{ +\dontrun{ +# This usually does nothing interactively, since the current +# (RStudio, RGui, Windows Terminal, etc.) window is on top. +ps_switch_to() + +# Try switching to another window, while the sleep is running, +# and then you'll see an alert for the current +# (RStudio, RGui, etc.) window +Sys.sleep(4); ps_switch_to() +} +} diff --git a/src/api-windows.c b/src/api-windows.c index dd806fdb..fda36618 100644 --- a/src/api-windows.c +++ b/src/api-windows.c @@ -1394,5 +1394,67 @@ SEXP psll_set_nice(SEXP p, SEXP value) { if (hProcess) CloseHandle(hProcess); ps__throw_error(); return R_NilValue; +} + +struct psll_windows_data { + int *pids; + int num_pids; + HWND handle; + SEXP result; +}; + +BOOL CALLBACK psll_switch_to_proc1( + _In_ HWND hwnd, + _In_ LPARAM lParam) { + struct psll_windows_data *data = (struct psll_windows_data*) lParam; + + DWORD processId; + DWORD ret = GetWindowThreadProcessId(hwnd, &processId); + if (!ret) return TRUE; + + int i; + for (i = 0; i < data->num_pids; i++) { + if (data->pids[i] == processId) { + data->handle = hwnd; + data->num_pids = i; + INTEGER(data->result)[0] = processId; + if (i == 0) return FALSE; else return TRUE; + } + } + + return TRUE; +} + +BOOL CALLBACK psll_switch_to_proc2( + _In_ HWND hwnd, + _In_ LPARAM lParam) { + struct psll_windows_data *data = (struct psll_windows_data*) lParam; + + if (hwnd == data->handle) { + BOOL ret = SetForegroundWindow(hwnd); + if (!ret) { + INTEGER(data->result)[0] = -INTEGER(data->result)[0]; + } + return FALSE; + } + + return TRUE; +} + +SEXP psll_switch_to(SEXP pids) { + struct psll_windows_data data; + data.pids = INTEGER(pids); + data.num_pids = LENGTH(pids); + data.handle = NULL; + data.result = PROTECT(allocVector(INTSXP, 1)); + INTEGER(data.result)[0] = 0; + EnumWindows(psll_switch_to_proc1, (LPARAM) &data); + + if (data.handle != NULL) { + EnumWindows(psll_switch_to_proc2, (LPARAM) &data); + } + + UNPROTECT(1); + return data.result; } diff --git a/src/dummy.c b/src/dummy.c index a5d7aa62..e3355ed6 100644 --- a/src/dummy.c +++ b/src/dummy.c @@ -39,6 +39,7 @@ SEXP psp__stat_st_rdev(SEXP x) { return ps__dummy("psp__stat_st_rdev"); } #ifndef PS__WINDOWS SEXP psw__realpath(SEXP x) { return ps__dummy("psw__realpath"); } SEXP psll_dlls(SEXP x) { return ps__dummy("psll_dlls"); } +SEXP psll_switch_to(SEXP x) { return ps__dummy("psll_switch_to"); } #endif #endif diff --git a/src/init.c b/src/init.c index d6a7491b..5a2d5d2a 100644 --- a/src/init.c +++ b/src/init.c @@ -55,6 +55,7 @@ static const R_CallMethodDef callMethods[] = { { "psll_get_nice", (DL_FUNC) psll_get_nice, 1 }, { "psll_set_nice", (DL_FUNC) psll_set_nice, 2 }, { "psll_dlls", (DL_FUNC) psll_dlls, 1 }, + { "psll_switch_to", (DL_FUNC) psll_switch_to, 1 }, /* Utils */ { "ps__init", (DL_FUNC) ps__init, 2 }, diff --git a/src/ps.h b/src/ps.h index 4a30f31e..29fc3f6c 100644 --- a/src/ps.h +++ b/src/ps.h @@ -43,6 +43,7 @@ SEXP psll_connections(SEXP p); SEXP psll_get_nice(SEXP p); SEXP psll_set_nice(SEXP p, SEXP value); SEXP psll_dlls(SEXP p); +SEXP psll_switch_to(SEXP plist); /* System API */ From c3747d5cdfe5b0b3e6be0d3cac12e04969f08fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Fri, 16 Oct 2020 16:34:42 +0100 Subject: [PATCH 2/3] Fix docs typo --- R/windows.R | 2 +- man/ps_switch_to.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/windows.R b/R/windows.R index fcaefa66..70b4d4f4 100644 --- a/R/windows.R +++ b/R/windows.R @@ -15,7 +15,7 @@ #' @return A named list: #' * `proc`: a process handle. This is the handle that ps tried to #' alert. It might that same as `p` or an ancestor, if -#' `ansectors = TRUE` was specified. This is `NULL` if ps did +#' `ancestors = TRUE` was specified. This is `NULL` if ps did #' not find any process with an associated Window to alert. #' * `success`: whether the Windows API call returned success. #' If this is `TRUE` that typically means that the alerted diff --git a/man/ps_switch_to.Rd b/man/ps_switch_to.Rd index b3ef77f5..b1fc70b8 100644 --- a/man/ps_switch_to.Rd +++ b/man/ps_switch_to.Rd @@ -19,7 +19,7 @@ A named list: \itemize{ \item \code{proc}: a process handle. This is the handle that ps tried to alert. It might that same as \code{p} or an ancestor, if -\code{ansectors = TRUE} was specified. This is \code{NULL} if ps did +\code{ancestors = TRUE} was specified. This is \code{NULL} if ps did not find any process with an associated Window to alert. \item \code{success}: whether the Windows API call returned success. If this is \code{TRUE} that typically means that the alerted From 58ed1761cc08d7f8f032d9a7ff85d1ce44e538a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Cs=C3=A1rdi?= Date: Wed, 28 Aug 2024 22:08:10 +0200 Subject: [PATCH 3/3] Add ps_switch_to() to pkgdown index --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 3fa4441d..0186d2a9 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -59,6 +59,7 @@ reference: - ps_set_cpu_affinity - ps_get_nice - ps_set_nice + - ps_switch_to - ps_wait - ps_windows_nice_values