mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
fix(electron): show window on cold-start toggle-visibility launch
Cold start: when superproductivity://toggle-visibility launches the app (it wasn't running), the freshly-shown window was immediately hidden again because the toggle saw it focused. Flag the cold-start URL during the argv scan and SHOW — never toggle — the window once it's ready (via processPendingProtocolUrls), respecting start-minimized-to-tray. The already-running second-instance path keeps real toggle behavior. Rename the two interactive protocol actions new-task/new-note to add-task/add-note: aligns with the app's Add-Task vocabulary and the globalAddTask/globalAddNote keys, and avoids colliding with the programmatic create-task/<title>. The action names are a frozen public contract once users bind them in compositor configs, so this is the pre-merge moment to settle the naming. Refs #7114.
This commit is contained in:
parent
b2876624b1
commit
aa74a08a34
3 changed files with 58 additions and 13 deletions
|
|
@ -120,8 +120,8 @@ Available fire-and-forget actions:
|
|||
| URL | Equivalent shortcut | Effect |
|
||||
| ----------------------------------------- | ----------------------- | -------------------------------------------- |
|
||||
| `superproductivity://toggle-visibility` | `globalShowHide` | Show/focus the window, or hide it |
|
||||
| `superproductivity://new-task` | `globalAddTask` | Show the window and open the add-task bar |
|
||||
| `superproductivity://new-note` | `globalAddNote` | Show the window and open the add-note dialog |
|
||||
| `superproductivity://add-task` | `globalAddTask` | Show the window and open the add-task bar |
|
||||
| `superproductivity://add-note` | `globalAddNote` | Show the window and open the add-note dialog |
|
||||
| `superproductivity://task-toggle-start` | `globalToggleTaskStart` | Start/pause tracking the current task |
|
||||
| `superproductivity://create-task/<title>` | — | Create a task with the URL-encoded `<title>` |
|
||||
|
||||
|
|
@ -137,20 +137,20 @@ Then bind them in your compositor. Examples:
|
|||
// Niri (config.kdl)
|
||||
binds {
|
||||
Mod+Shift+S { spawn "xdg-open" "superproductivity://toggle-visibility"; }
|
||||
Mod+Shift+A { spawn "xdg-open" "superproductivity://new-task"; }
|
||||
Mod+Shift+A { spawn "xdg-open" "superproductivity://add-task"; }
|
||||
}
|
||||
```
|
||||
|
||||
```ini
|
||||
# sway / i3 (~/.config/sway/config)
|
||||
bindsym $mod+Shift+s exec xdg-open "superproductivity://toggle-visibility"
|
||||
bindsym $mod+Shift+a exec xdg-open "superproductivity://new-task"
|
||||
bindsym $mod+Shift+a exec xdg-open "superproductivity://add-task"
|
||||
```
|
||||
|
||||
```ini
|
||||
# Hyprland (~/.config/hypr/hyprland.conf)
|
||||
bind = $mainMod SHIFT, S, exec, xdg-open "superproductivity://toggle-visibility"
|
||||
bind = $mainMod SHIFT, A, exec, xdg-open "superproductivity://new-task"
|
||||
bind = $mainMod SHIFT, A, exec, xdg-open "superproductivity://add-task"
|
||||
```
|
||||
|
||||
If `xdg-open` does not reach the app, confirm the desktop entry registers the scheme with `xdg-mime query default x-scheme-handler/superproductivity` (it should print Super Productivity's `.desktop` file). The same mechanism works on X11; it is only required on Wayland.
|
||||
|
|
|
|||
|
|
@ -62,21 +62,21 @@ test.beforeEach(() => {
|
|||
logCalls = [];
|
||||
});
|
||||
|
||||
test('new-task shows the window and opens the add-task bar', () => {
|
||||
test('add-task shows the window and opens the add-task bar', () => {
|
||||
const { processProtocolUrl } = loadModule();
|
||||
const win = makeWin();
|
||||
|
||||
processProtocolUrl('superproductivity://new-task', win);
|
||||
processProtocolUrl('superproductivity://add-task', win);
|
||||
|
||||
assert.equal(showOrFocusCalls.length, 1);
|
||||
assert.deepEqual(win.sent, [{ channel: 'SHOW_ADD_TASK_BAR', payload: undefined }]);
|
||||
});
|
||||
|
||||
test('new-note shows the window and triggers add-note', () => {
|
||||
test('add-note shows the window and triggers add-note', () => {
|
||||
const { processProtocolUrl } = loadModule();
|
||||
const win = makeWin();
|
||||
|
||||
processProtocolUrl('superproductivity://new-note', win);
|
||||
processProtocolUrl('superproductivity://add-note', win);
|
||||
|
||||
assert.equal(showOrFocusCalls.length, 1);
|
||||
assert.deepEqual(win.sent, [{ channel: 'ADD_NOTE', payload: undefined }]);
|
||||
|
|
@ -190,8 +190,33 @@ test('second-instance pre-focuses for a plain launch and for non-toggle actions'
|
|||
app.handlers['second-instance']({}, ['/path/to/app']);
|
||||
assert.equal(showOrFocusCalls.length, 1);
|
||||
|
||||
// b) new-task still focuses the window and opens the add-task bar.
|
||||
app.handlers['second-instance']({}, ['/path/to/app', 'superproductivity://new-task']);
|
||||
// b) add-task still focuses the window and opens the add-task bar.
|
||||
app.handlers['second-instance']({}, ['/path/to/app', 'superproductivity://add-task']);
|
||||
assert.ok(showOrFocusCalls.length >= 2, 'non-toggle action still focuses the window');
|
||||
assert.deepEqual(win.sent, [{ channel: 'SHOW_ADD_TASK_BAR', payload: undefined }]);
|
||||
});
|
||||
|
||||
test('cold-start toggle-visibility shows the launched window instead of toggling it (#7114)', () => {
|
||||
const win = makeWin();
|
||||
const app = makeFakeApp();
|
||||
const originalArgv = process.argv;
|
||||
// Simulate the app being COLD-LAUNCHED by the URL: it appears in argv at startup.
|
||||
process.argv = ['/path/to/app', 'superproductivity://toggle-visibility'];
|
||||
let mod;
|
||||
try {
|
||||
mod = loadModule();
|
||||
mod.initializeProtocolHandling(false, app, () => win);
|
||||
} finally {
|
||||
process.argv = originalArgv;
|
||||
}
|
||||
|
||||
// The window is created + shown by startup, then the ready-drain runs ~1s later.
|
||||
mod.processPendingProtocolUrls(win);
|
||||
|
||||
// Cold start must SHOW the window, never route it through the toggle (which, on a freshly
|
||||
// shown+focused window, would immediately hide it again).
|
||||
assert.equal(toggleVisibilityCalls.length, 0, 'cold-start must not toggle');
|
||||
assert.equal(showOrFocusCalls.length, 1);
|
||||
assert.equal(showOrFocusCalls[0], win);
|
||||
assert.deepEqual(win.sent, []);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ export const PROTOCOL_PREFIX = `${PROTOCOL_NAME}://`;
|
|||
// Store pending URLs to process after window is ready
|
||||
let pendingUrls: string[] = [];
|
||||
|
||||
// When the app is COLD-LAUNCHED by `superproductivity://toggle-visibility` (it was not
|
||||
// already running), the freshly-created window must just be SHOWN — never toggled, which
|
||||
// would immediately hide the window the launch was meant to reveal (#7114). The cold-start
|
||||
// argv scan sets this one-shot flag instead of routing that URL through the toggle, and the
|
||||
// window-ready drain (processPendingProtocolUrls) consumes it with a single showOrFocus.
|
||||
let coldStartShowPending = false;
|
||||
|
||||
/**
|
||||
* Parse the action (host) of a `superproductivity://` URL, or `null` if it is
|
||||
* missing/unparseable. Used by the `second-instance` handler to special-case actions
|
||||
|
|
@ -85,11 +92,11 @@ export const processProtocolUrl = (url: string, mainWin: BrowserWindow | null):
|
|||
case 'toggle-visibility':
|
||||
toggleWindowVisibility(mainWin);
|
||||
break;
|
||||
case 'new-note':
|
||||
case 'add-note':
|
||||
showOrFocus(mainWin);
|
||||
mainWin.webContents.send(IPC.ADD_NOTE);
|
||||
break;
|
||||
case 'new-task':
|
||||
case 'add-task':
|
||||
showOrFocus(mainWin);
|
||||
mainWin.webContents.send(IPC.SHOW_ADD_TASK_BAR);
|
||||
break;
|
||||
|
|
@ -102,6 +109,12 @@ export const processProtocolUrl = (url: string, mainWin: BrowserWindow | null):
|
|||
};
|
||||
|
||||
export const processPendingProtocolUrls = (mainWin: BrowserWindow): void => {
|
||||
if (coldStartShowPending) {
|
||||
coldStartShowPending = false;
|
||||
// Cold-start toggle-visibility: show the window (works even if start-minimized-to-tray
|
||||
// left it hidden) instead of toggling it back off.
|
||||
showOrFocus(mainWin);
|
||||
}
|
||||
if (pendingUrls.length > 0) {
|
||||
log(`Processing ${pendingUrls.length} pending protocol URLs`);
|
||||
const urls = [...pendingUrls];
|
||||
|
|
@ -168,6 +181,13 @@ export const initializeProtocolHandling = (
|
|||
'Protocol URL from command line:',
|
||||
`${PROTOCOL_PREFIX}${getProtocolAction(val) ?? ''}`,
|
||||
);
|
||||
// A toggle-visibility that cold-launched the app must SHOW the new window, not toggle
|
||||
// it (see coldStartShowPending) — running the normal toggle would hide the window the
|
||||
// user just asked to see (#7114). Flag it for the window-ready drain instead.
|
||||
if (getProtocolAction(val) === 'toggle-visibility') {
|
||||
coldStartShowPending = true;
|
||||
return;
|
||||
}
|
||||
// Process after app is ready
|
||||
appInstance.whenReady().then(() => {
|
||||
processProtocolUrl(val, getMainWindow());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue