The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.
This reference file serves as the definitive engineering and behavioral blueprint for AI coding agents developing, debugging, or maintaining RDesk applications. It maps out the package’s multi-directory structure, core contracts, API behaviors, design constraints, and common implementation pitfalls.
RDesk applications utilize a dual-process desktop architecture:
[rdesk-launcher.exe] (Frontend Shell) <--Standard I/O Pipes--> [R Process] (Backend Logic)
- WebView2 rendering control - R6 event loop (App$run())
- Virtual HTTPS folder mapping - Logic handlers (on_message())
- Native windows, tray & menus - Background tasks (mirai)
- Watchdog process cleanup - Offline computation
R/server.R) and UI assets (www/). Developers
only write and modify this layer.async(), rdesk_auto_update(), and
hot-reloading watchers.App class that services messaging callbacks.httpuv,
WebSockets). Communication uses standard I/O streams. Never
introduce code that opens local TCP/UDP ports.Every RDesk application must adhere to this standardized file layout:
MyApp/
├── app.R # Main application entry point (keep thin)
├── DESCRIPTION # Application metadata and dependency list
├── R/ # Backend R modules
│ ├── server.R # Message handlers & initialization (primary logic file)
│ ├── data.R # Data loading, extraction, and transformation
│ └── plots.R # ggplot2 and base chart generation functions
└── www/ # Frontend UI assets
├── index.html # HTML5 markup structure
├── css/
│ └── style.css # Application styling
└── js/
├── app.js # Client event handlers and UI updates
└── rdesk.js # RDesk frontend runtime bridge (NEVER EDIT THIS FILE)
app.R Entry Point:app_dir <- tryCatch(
if (nzchar(Sys.getenv("R_BUNDLE_APP"))) getwd()
else dirname(rstudioapi::getActiveDocumentContext()$path),
error = function(e) getwd()
)
library(RDesk)
# Dynamically source all backend modules
lapply(
list.files(file.path(app_dir, "R"), pattern = "\\.R$", full.names = TRUE),
source
)
app <- App$new(
title = "My RDesk App",
width = 1100L,
height = 740L,
www = file.path(app_dir, "www")
)
init_handlers(app)
app$run()app.R. Sourcing modular R files and calling
init_handlers(app) must be the only operations in
app.R.lapply(list.files(...)) pattern. Individual explicit
source() calls by filename break RDesk’s directory
packaging and hot-reloading hooks.www/js/rdesk.js is
the framework client library. Never import it from a CDN or alter its
contents.All messaging across the R-to-JS bridge uses this exact JSON envelope structure:
{
"id": "msg_unique_hash",
"type": "message_type",
"version": "1.0",
"payload": {},
"timestamp": 1716382104.123
}"foo" automatically returns its response payload to a JS
listener registered for "foo_result".on_message()
handlers must return a standard R list or
NULL. The framework automatically serialises and wraps the
output in the envelope. Do not wrap return values in
rdesk_message() inside handlers.app$send() on the R side, and
rdesk.send() on the JS side.__loading__,
__progress__, __reload_ui__) are reserved for
core framework signaling. Never define custom application messages with
the __ prefix.R/server.R)Define event handlers inside init_handlers(app) in
R/server.R.
init_handlers <- function(app) {
# Triggered once when the UI window is fully initialized
app$on_ready(function() {
# Define native window menu
app$set_menu(list(
File = list(
"Open..." = function() app$send("trigger_open", list()),
"---",
"Exit" = app$quit
),
Help = list(
"About" = function() app$toast("RDesk v1.0.5", type = "info")
)
))
# Push initial dataset
df <- load_initial_data()
app$send("data_ready", rdesk_df_to_list(df))
})
# Standard request-response handler
app$on_message("get_filtered_data", function(payload) {
# payload matches the JS payload object
df <- load_subset(payload$filter_val)
rdesk_df_to_list(df)
})
}app$toast("message", type = "success" | "error" | "info" | "warning")app$dialog_open(title = "Open", filters = "CSV (*.csv)|*.csv")
-> Returns character path or NULLapp$dialog_save(title = "Save", filters = "CSV (*.csv)|*.csv", default = "export.csv")app$dialog_folder(title = "Select Folder")app$loading_start("text", cancellable = TRUE),
app$loading_progress(50),
app$loading_done()To prevent blocking the single-threaded R event loop during computation, RDesk utilizes a three-tier async architecture:
async())Applies to 95% of background processing needs. Automatically manages loading overlays, routing, and task cancellation.
app$on_message("compute_stats", async(function(payload) {
# Executed inside an isolated background worker process
# DO NOT call app$ methods inside this closure
# DO NOT access global variables; pass them via payload or capture them explicitly
df <- load_raw_data(payload$source)
result <- run_calculation(df)
list(stats = result)
}, app = app, loading_message = "Processing calculation..."))To push real-time progress updates from a background worker:
rdesk_async())Used when fine-grained callback control is required.
Communicate with the backend R process using the globally available
rdesk object:
// Wrap initialization inside ready() to ensure bridge is fully established
rdesk.ready(function() {
rdesk.send("get_initial_state", {})
.then(res => renderUI(res))
.catch(err => console.error(err));
});
// Listen for push notifications from R
rdesk.on("update_data", function(data) {
updateChart(data);
});
// Clean up listeners
rdesk.off("update_data");Base64 Charts:
Structured Tables:
send() calls inside
rdesk.ready(). Calling send() before the
native bridge is established throws silent errors.fetch(),
XMLHttpRequest, or WebSocket to communicate
with the R process. Always use rdesk.send() and
rdesk.on().Convert plots to strings for transmission over the standard input/output pipe:
# In R/plots.R
generate_trend_plot <- function(df) {
p <- ggplot2::ggplot(df, ggplot2::aes(x = date, y = value)) +
ggplot2::geom_line(colour = "#1a1a2e", linewidth = 1) +
ggplot2::theme_minimal()
rdesk_plot_to_base64(p)
}
# In R/server.R handler
app$on_message("get_trend", function(payload) {
df <- load_data()
list(chart = generate_trend_plot(df))
})| Shiny Concept | RDesk Desktop Equivalent |
|---|---|
input$x |
payload$x inside
app$on_message("x_changed", ...) |
reactive({...}) |
Standard R function called explicitly by the handler |
renderPlot({...}) |
rdesk_plot_to_base64() returned inside a
list |
renderTable({...}) |
rdesk_df_to_list() returned inside a
list |
observeEvent(input$x, {...}) |
app$on_message("x_changed", function(payload) {...}) |
showNotification() |
app$toast() |
withProgress() |
async(..., loading_message = "...") |
fileInput() |
app$dialog_open() |
downloadHandler() |
app$dialog_save() |
# --- 1. SHINY ORIGINAL (server.R) ---
server <- function(input, output, session) {
filtered <- reactive({
mtcars[mtcars$cyl == input$cyl_filter, ]
})
output$scatter <- renderPlot({
ggplot2::ggplot(filtered(), ggplot2::aes(wt, mpg)) + ggplot2::geom_point()
})
output$table <- renderTable({ filtered() })
}# --- 2. RDESK REWRITE (R/server.R) ---
init_handlers <- function(app) {
app$on_ready(function() {
app$send("data_ready", rdesk_df_to_list(mtcars))
})
app$on_message("filter_changed", async(function(payload) {
df <- mtcars[mtcars$cyl == payload$cyl_filter, ]
p <- ggplot2::ggplot(df, ggplot2::aes(wt, mpg)) + ggplot2::geom_point()
list(
chart = rdesk_plot_to_base64(p),
table = rdesk_df_to_list(df)
)
}, app = app))
}// --- 3. RDESK FRONTEND (www/js/app.js) ---
rdesk.ready(function() {
rdesk.send("get_data", {});
});
document.getElementById("cyl-filter").addEventListener("change", function() {
rdesk.send("filter_changed", { cyl_filter: parseInt(this.value) });
});
rdesk.on("filter_changed_result", function(data) {
document.getElementById("chart").src = "data:image/png;base64," + data.chart;
renderTable(data.table.rows, data.table.cols);
});async()
workers# WRONG
app$on_message("task", async(function(payload) {
res <- run_calculation(payload)
app$toast("Task Completed!") # ERROR: app$ is not available in isolated worker processes
list(data = res)
}, app = app))
# CORRECT
app$on_message("task", async(function(payload) {
res <- run_calculation(payload)
list(data = res) # Return value is pushed automatically to task_result
}, app = app))# WRONG
dataset <- load_heavy_data()
app$on_message("run", async(function(payload) {
process(dataset) # ERROR: dataset is not bound in the worker process environment
}, app = app))
# CORRECT
dataset <- load_heavy_data()
app$on_message("run", async(function(payload) {
process(payload$data) # Pass the object explicitly via payload
}, app = app))
# OR
local_data <- dataset
app$on_message("run", async(function(payload) {
process(local_data) # Captured in the closure, serialised to the worker
}, app = app))installed.packages() to check
dependenciesapp.R launches the application window. Close the window,
edit files in R/server.R or www/, and source
app.R again.rdesk_watch(app) during development to live-reload UI
assets when saved.ci_mode)To run unit tests on message handlers without spawning a browser window:
renvrenv is active, RDesk automatically detects the
local environment.build_app() writes a frozen renv.lock into
the distributable, copying your exact package versions to ensure ABI
compatibility and prevent package-mismatch crashes.# Create scaffolding
rdesk_create_app("MyApp")
# Core Window Setup
app <- App$new(title = "App", width = 1100L, height = 740L, www = "www/")
app$on_ready(function() { ... })
app$on_message("msg_type", function(payload) { list(...) })
app$run()
# Server-Side Push
app$send("type", list(key = value))
# Dialogs
app$dialog_open(title = "Open File", filters = "CSV (*.csv)|*.csv")
app$dialog_save(title = "Save File", filters = "CSV (*.csv)|*.csv", default = "data.csv")
app$dialog_folder(title = "Select Export Folder")
# System Control
app$toast("message", type = "success|error|info|warning")
app$notify("Title", "Body Text")
app$maximize()
app$quit()
# Loading States
app$loading_start("Message...", cancellable = TRUE, job_id = "job_1")
app$loading_progress(50)
app$loading_done()
# Async Operations
rdesk_async(task, args, on_done, on_error)
rdesk_cancel_job("job_id")
rdesk_jobs_pending()// Send data to R, returns Promise
rdesk.send("msg_type", { payload_key: "value" })
.then(res => { ... })
.catch(err => { ... });
// Listen for push notifications from R
rdesk.on("push_type", function(data) { ... });
// Run when the browser bridge is active
rdesk.ready(function() { ... });
// Deregister listener
rdesk.off("push_type");These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.