Desktop: Resolves #12006: Add a new menu item to launch the primary instance from the secondary one

pull/12080/head
Laurent Cozic 2025-04-09 08:14:17 +01:00
parent 587db433a8
commit 52ffd46a6a
11 changed files with 67 additions and 19 deletions

View File

@ -158,9 +158,10 @@ packages/app-desktop/commands/exportFolders.js
packages/app-desktop/commands/exportNotes.js
packages/app-desktop/commands/focusElement.js
packages/app-desktop/commands/index.js
packages/app-desktop/commands/newAppInstance.js
packages/app-desktop/commands/openNoteInNewWindow.js
packages/app-desktop/commands/openPrimaryAppInstance.js
packages/app-desktop/commands/openProfileDirectory.js
packages/app-desktop/commands/openSecondaryAppInstance.js
packages/app-desktop/commands/replaceMisspelling.js
packages/app-desktop/commands/restoreNoteRevision.js
packages/app-desktop/commands/startExternalEditing.js

3
.gitignore vendored
View File

@ -133,9 +133,10 @@ packages/app-desktop/commands/exportFolders.js
packages/app-desktop/commands/exportNotes.js
packages/app-desktop/commands/focusElement.js
packages/app-desktop/commands/index.js
packages/app-desktop/commands/newAppInstance.js
packages/app-desktop/commands/openNoteInNewWindow.js
packages/app-desktop/commands/openPrimaryAppInstance.js
packages/app-desktop/commands/openProfileDirectory.js
packages/app-desktop/commands/openSecondaryAppInstance.js
packages/app-desktop/commands/replaceMisspelling.js
packages/app-desktop/commands/restoreNoteRevision.js
packages/app-desktop/commands/startExternalEditing.js

View File

@ -655,7 +655,7 @@ export default class ElectronAppWrapper {
// might still be there for a short while.
await msleep(1000);
this.ipcLogger_.warn('restartAltInstance: App is gone - restarting it');
void bridge().launchNewAppInstance(this.env());
void bridge().launchAltAppInstance(this.env());
} else {
this.ipcLogger_.warn('restartAltInstance: Could not restart calling app because it was still open');
}

View File

@ -523,12 +523,18 @@ export class Bridge {
}
}
public async launchNewAppInstance(env: string) {
public async launchAltAppInstance(env: string) {
const cmd = this.appLaunchCommand(env, 'alt1');
await execCommand([cmd.execPath].concat(cmd.args), { detached: true });
}
public async launchMainAppInstance(env: string) {
const cmd = this.appLaunchCommand(env, '');
await execCommand([cmd.execPath].concat(cmd.args), { detached: true });
}
public async restart() {
// Note that in this case we are not sending the "appClose" event
// to notify services and component that the app is about to close

View File

@ -6,7 +6,8 @@ import * as exportDeletionLog from './exportDeletionLog';
import * as exportFolders from './exportFolders';
import * as exportNotes from './exportNotes';
import * as focusElement from './focusElement';
import * as newAppInstance from './newAppInstance';
import * as openSecondaryAppInstance from './openSecondaryAppInstance';
import * as openPrimaryAppInstance from './openPrimaryAppInstance';
import * as openNoteInNewWindow from './openNoteInNewWindow';
import * as openProfileDirectory from './openProfileDirectory';
import * as replaceMisspelling from './replaceMisspelling';
@ -29,7 +30,8 @@ const index: any[] = [
exportFolders,
exportNotes,
focusElement,
newAppInstance,
openSecondaryAppInstance,
openPrimaryAppInstance,
openNoteInNewWindow,
openProfileDirectory,
replaceMisspelling,

View File

@ -0,0 +1,19 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import bridge from '../services/bridge';
import Setting from '@joplin/lib/models/Setting';
export const declaration: CommandDeclaration = {
name: 'openPrimaryAppInstance',
label: () => _('Open primary app instance...'),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await bridge().launchMainAppInstance(Setting.value('env'));
},
enabledCondition: 'isAltInstance',
};
};

View File

@ -4,14 +4,14 @@ import bridge from '../services/bridge';
import Setting from '@joplin/lib/models/Setting';
export const declaration: CommandDeclaration = {
name: 'newAppInstance',
label: () => _('New application instance...'),
name: 'openSecondaryAppInstance',
label: () => _('Open secondary app instance...'),
};
export const runtime = (): CommandRuntime => {
return {
execute: async (_context: CommandContext) => {
await bridge().launchNewAppInstance(Setting.value('env'));
await bridge().launchAltAppInstance(Setting.value('env'));
},
enabledCondition: '!isAltInstance',

View File

@ -555,7 +555,8 @@ function useMenu(props: Props) {
const newFolderItem = menuItemDic.newFolder;
const newSubFolderItem = menuItemDic.newSubFolder;
const printItem = menuItemDic.print;
const newAppInstance = menuItemDic.newAppInstance;
const openSecondaryAppInstance = menuItemDic.openSecondaryAppInstance;
const openPrimaryAppInstance = menuItemDic.openPrimaryAppInstance;
const switchProfileItem = {
label: _('Switch profile'),
submenu: switchProfileMenuItems,
@ -723,7 +724,8 @@ function useMenu(props: Props) {
type: 'separator',
},
switchProfileItem,
newAppInstance,
openSecondaryAppInstance,
openPrimaryAppInstance,
],
};

View File

@ -48,7 +48,8 @@ export default function() {
'toggleTabMovesFocus',
'editor.deleteLine',
'editor.duplicateLine',
'newAppInstance',
'openSecondaryAppInstance',
'openPrimaryAppInstance',
// We cannot put the undo/redo commands in the menu because they are
// editor-specific commands. If we put them there it will break the
// undo/redo in regular text fields.

View File

@ -14,13 +14,17 @@ interface ExecCommandOptions {
}
export default async (command: string | string[], options: ExecCommandOptions | null = null): Promise<string> => {
const detached = options ? options.detached : false;
// When launching a detached executable it's better not to pipe the stdout and stderr, as this
// will most likely cause an EPIPE error.
options = {
showInput: true,
showStdout: true,
showStderr: true,
showInput: !detached,
showStdout: !detached,
showStderr: !detached,
quiet: false,
env: {},
detached: false,
...options,
};

View File

@ -17,9 +17,9 @@ Each instance is completely isolated, operating as a standalone version of Jopli
Currently, Joplin supports up to **two running instances**:
1. **Main Instance**: The primary application instance, with full access to all Joplin features.
1. **Primary Instance**: The primary application instance, with full access to all Joplin features.
2. **Alternative Instance**: A secondary application instance that functions independently. However, it does not support the **Web Clipper service**, which can only run in the main instance.
2. **Secondary Instance**: A secondary application instance that functions independently. However, it does not support the **Web Clipper service**, which can only run in the main instance.
## How to Launch a Second Instance
@ -29,8 +29,20 @@ To start a second instance of Joplin:
2. Navigate to the menu and select:
**File** => **New application instance...**
**File** => **Open secondary app instance...**
3. A new instance of Joplin will open with its own profile.
This second instance operates independently, allowing you to customise it as needed.
## Caveats
### Launching the primary instance when the secondary instance is active
Technically, the secondary instance is still initiated from the same executable file, which might confuse the operating system. Most operating systems reasonably assume that if you attempt to launch a GUI application that is already running, your intention is to bring that application into focus.
In practical terms, this means the following:
If you close the primary instance while the secondary instance remains open, and then attempt to reopen the primary instance—for instance, by clicking on its icon—the operating system will most likely refocus on the secondary instance instead of launching the primary one. To address this issue, the secondary instance includes a menu item labelled **Open primary app instance...**. Clicking on this option will explicitly launch the primary instance.
In the same way, the secondary instance should generally be launched only from the first one, using the **Open secondary app instance...** menu item.