Skip to content
Draft
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
10 changes: 9 additions & 1 deletion src/gui/src/UI/UIWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ async function UIWindow(options) {
options.window_class = (options.window_class !== undefined ? ' ' + options.window_class : '');

options.is_visible = options.is_visible ?? true;
options.background = options.background ?? false;

// if only one instance is allowed, bring focus to the window that is already open
if(options.single_instance && options.app !== ''){
Expand Down Expand Up @@ -579,7 +580,8 @@ async function UIWindow(options) {
$(el_window_head_icon).attr('src', window.icons['shared.svg']);
}
// focus on this window and deactivate other windows
if ( options.is_visible ) {
// BUT: Don't focus if this is a background app - background apps should not steal focus
if ( options.is_visible && !options.background ) {
$(el_window).focusWindow();
}

Expand Down Expand Up @@ -1410,7 +1412,10 @@ async function UIWindow(options) {
el_window_app_iframe.contentWindow.postMessage({msg: "drop", x: (window.mouseX - rect.left), y: (window.mouseY - rect.top), items: items}, '*');

// bring focus to this window
// BUT: Don't focus if this is a background app - background apps should not steal focus
if ( !options.background ) {
$(el_window).focusWindow();
}
}

// if this window is not a directory, cancel drop.
Expand Down Expand Up @@ -1552,10 +1557,13 @@ async function UIWindow(options) {
// make sure to cancel any previous timeouts otherwise the window will be brought to front multiple times
clearTimeout(drag_enter_timeout);
// If items are dragged over this window long enough, bring it to front
// BUT: Don't focus if this is a background app - background apps should not steal focus
if ( !options.background ) {
drag_enter_timeout = setTimeout(function(){
// focus window
$(el_window).focusWindow();
}, 1400);
}
},
leave: function (dragsterEvent, event) {
// cancel the timeout for 'bringing window to front'
Expand Down
9 changes: 8 additions & 1 deletion src/gui/src/helpers/launch_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ const launch_app = async (options)=>{
app_info.uuid = app_info.uuid ?? app_info.uid;
app_info.uid = app_info.uid ?? app_info.uuid;

// Allow callers to override background setting (e.g., when launched from terminal with & operator)
if (options.background !== undefined) {
app_info.background = options.background;
}

// If no `options.name` is provided, use the app name from the app_info
options.name = options.name ?? app_info.name;

Expand Down Expand Up @@ -346,6 +351,7 @@ const launch_app = async (options)=>{
is_visible: ! app_info.background,
is_maximized: options.maximized,
is_fullpage: options.is_fullpage,
background: app_info.background, // Pass background flag to UIWindow
...(options.pseudonym ? {pseudonym: options.pseudonym} : {}),
...window_options,
is_resizable: window_resizable,
Expand Down Expand Up @@ -400,7 +406,8 @@ const launch_app = async (options)=>{

// If `window-active` is set (meanign the window is focused), focus the window one more time
// this is to ensure that the iframe is `definitely` focused and can receive keyboard events (e.g. keydown)
if($(process.references.el_win).hasClass('window-active')){
// BUT: Don't focus if this is a background app - background apps should not steal focus
if($(process.references.el_win).hasClass('window-active') && !app_info.background){
$(process.references.el_win).focusWindow();
}
});
Expand Down
14 changes: 12 additions & 2 deletions src/gui/src/services/ExecService.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class ExecService extends Service {
}

// This method is exposed to apps via IPCService.
async launchApp ({ app_name, args, pseudonym }, { ipc_context, msg_id } = {}) {
async launchApp ({ app_name, args, pseudonym, background }, { ipc_context, msg_id } = {}) {
const app = ipc_context?.caller?.app;
const process = ipc_context?.caller?.process;

Expand All @@ -44,6 +44,14 @@ export class ExecService extends Service {

this.log.info('launchApp connection', connection);

// Fetch app info to check if it's a background app
let app_info;
if (app_name !== 'explorer') {
app_info = await puter.apps.get(app_name);
} else {
app_info = [];
}

const params = {};
for ( const provider of this.param_providers ) {
Object.assign(params, provider());
Expand All @@ -55,6 +63,7 @@ export class ExecService extends Service {
name: app_name,
pseudonym,
args: args ?? {},
background: background,
parent_instance_id: app?.appInstanceID,
uuid: child_instance_id,
params,
Expand Down Expand Up @@ -87,7 +96,8 @@ export class ExecService extends Service {

// If `window-active` is set (meanign the window is focused), focus the window one more time
// this is to ensure that the iframe is `definitely` focused and can receive keyboard events (e.g. keydown)
if($(child_process.references.el_win).hasClass('window-active')){
// BUT: Don't focus if this is a background app - background apps should not steal focus
if($(child_process.references.el_win).hasClass('window-active') && !app_info.background){
$(child_process.references.el_win).focusWindow();
}
});
Expand Down
1 change: 1 addition & 0 deletions src/phoenix/src/ansi-shell/parsing/buildParserFirstHalf.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export const buildParserFirstHalf = (sp, profile) => {
parserBuilder.def(a => a.literal('|').assign({ $: 'op.pipe' })),
parserBuilder.def(a => a.literal('>').assign({ $: 'op.redirect', direction: 'out' })),
parserBuilder.def(a => a.literal('<').assign({ $: 'op.redirect', direction: 'in' })),
parserBuilder.def(a => a.literal('&').assign({ $: 'op.background' })),
{
parser: parserBuilder.def(a => a.literal(')').assign({ $: 'op.close' })),
transition: {
Expand Down
7 changes: 7 additions & 0 deletions src/phoenix/src/ansi-shell/parsing/buildParserSecondHalf.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class ShellConstructsPStratumImpl {
node.tokens = [];
node.inputRedirects = [];
node.outputRedirects = [];
node.background = false;
},
next ({ value, lexer }) {
if ( value.$ === 'op.line-terminator' ) {
Expand All @@ -137,6 +138,12 @@ class ShellConstructsPStratumImpl {
this.pop();
return;
}
if ( value.$ === 'op.background' ) {
// Mark this command as background and consume the operator
this.stack_top.node.background = true;
lexer.next();
return;
}
if ( value.$ === 'op.redirect' ) {
this.push('redirect', { direction: value.direction });
lexer.next();
Expand Down
10 changes: 9 additions & 1 deletion src/phoenix/src/ansi-shell/pipeline/Pipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,16 @@ export class PreparedCommand {
// args: ast.args.map(node => node.text),
inputRedirect,
outputRedirects,
background: ast.background || false,
});
}

constructor ({ command, args, inputRedirect, outputRedirects }) {
constructor ({ command, args, inputRedirect, outputRedirects, background }) {
this.command = command;
this.args = args;
this.inputRedirect = inputRedirect;
this.outputRedirects = outputRedirects;
this.background = background || false;
}

setContext (ctx) {
Expand Down Expand Up @@ -234,6 +236,12 @@ export class PreparedCommand {
command,
args,
outputIsRedirected: this.outputRedirects.length > 0,
background: this.background,
},
env: {
...this.ctx.env,
// Set BACKGROUND environment variable if command is run in background
...(this.background ? { BACKGROUND: '1' } : {}),
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ export class PuterAppCommandProvider {
},
env: {...ctx.env},
};
const child = await puter.ui.launchApp(id, args);
// Check if app should be launched in background
// The & operator sets BACKGROUND=1 in the environment, or we can check ctx.locals.background
const shouldRunInBackground = ctx.env.BACKGROUND === '1' || ctx.env.BACKGROUND === 'true' || ctx.locals.background === true;
const child = await puter.ui.launchApp(id, args, undefined, { background: shouldRunInBackground });

const resize_listener = evt => {
child.postMessage({
Expand Down
3 changes: 2 additions & 1 deletion src/puter-js/src/modules/UI.js
Original file line number Diff line number Diff line change
Expand Up @@ -1009,7 +1009,7 @@ class UI extends EventListener {
}

// Returns a Promise<AppConnection>
launchApp = async function launchApp(app_name, args, callback) {
launchApp = async function launchApp(app_name, args, callback, options) {
let pseudonym = undefined;
if ( app_name.includes('#(as)') ) {
[app_name, pseudonym] = app_name.split('#(as)');
Expand All @@ -1021,6 +1021,7 @@ class UI extends EventListener {
app_name,
pseudonym,
args,
background: options?.background,
},
});

Expand Down