Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ps_switch_to() #100

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export(ps_shared_lib_users)
export(ps_shared_libs)
export(ps_status)
export(ps_suspend)
export(ps_switch_to)
export(ps_system_cpu_times)
export(ps_system_memory)
export(ps_system_swap)
Expand Down
60 changes: 60 additions & 0 deletions R/windows.R
Original file line number Diff line number Diff line change
@@ -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
#' `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
#' 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")

Check warning on line 42 in R/windows.R

View check run for this annotation

Codecov / codecov/patch

R/windows.R#L40-L42

Added lines #L40 - L42 were not covered by tests
}
assert_ps_handle(p)

Check warning on line 44 in R/windows.R

View check run for this annotation

Codecov / codecov/patch

R/windows.R#L44

Added line #L44 was not covered by tests

if (ancestors) {
plist <- ps_descent(p)

Check warning on line 47 in R/windows.R

View check run for this annotation

Codecov / codecov/patch

R/windows.R#L46-L47

Added lines #L46 - L47 were not covered by tests
} else {
plist <- list(p)

Check warning on line 49 in R/windows.R

View check run for this annotation

Codecov / codecov/patch

R/windows.R#L49

Added line #L49 was not covered by tests
}
pids <- map_int(plist, ps_pid)

Check warning on line 51 in R/windows.R

View check run for this annotation

Codecov / codecov/patch

R/windows.R#L51

Added line #L51 was not covered by tests

ret <- .Call(psll_switch_to, pids)
if (ret == 0) {
list(proc = NULL, success = FALSE)

Check warning on line 55 in R/windows.R

View check run for this annotation

Codecov / codecov/patch

R/windows.R#L53-L55

Added lines #L53 - L55 were not covered by tests
} else {
# -pid on failure
list(proc = plist[[match(abs(ret), pids)]], success = ret > 0)

Check warning on line 58 in R/windows.R

View check run for this annotation

Codecov / codecov/patch

R/windows.R#L58

Added line #L58 was not covered by tests
}
}
1 change: 1 addition & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ reference:
- ps_set_cpu_affinity
- ps_get_nice
- ps_set_nice
- ps_switch_to
- ps_wait
- ps_windows_nice_values

Expand Down
50 changes: 50 additions & 0 deletions man/ps_switch_to.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions src/api-windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -1740,7 +1740,69 @@ 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;
}


Expand Down
1 change: 1 addition & 0 deletions src/dummy.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
#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"); }

Check warning on line 53 in src/dummy.c

View check run for this annotation

Codecov / codecov/patch

src/dummy.c#L53

Added line #L53 was not covered by tests
#endif
#endif

Expand Down
1 change: 1 addition & 0 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,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 },
{ "psll_get_cpu_aff", (DL_FUNC) psll_get_cpu_aff, 1 },
{ "psll_set_cpu_aff", (DL_FUNC) psll_set_cpu_aff, 2 },
{ "psll_wait", (DL_FUNC) psll_wait, 2 },
Expand Down
1 change: 1 addition & 0 deletions src/ps.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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);
SEXP psll_get_cpu_aff(SEXP p);
SEXP psll_set_cpu_aff(SEXP p, SEXP affinity);
SEXP psll_wait(SEXP p, SEXP timeout);
Expand Down
Loading