Open porst17 opened 5 years ago
Is this:
Opening an additional BrowserWindow of the kiosk browser/electron that is semi-transparent, always-on-top and covers only a small part of the screen
even possible in electron? Particularly always-on-top + transparent are things very much OS / Window manager dependent... not something you see often on Mac OS for instance... so I'd be a bit surprised that electron allows this. If it does, no problem.
On the other hand, having a plugin that hooks into executable app launch and launches a native program at the same time (the "close" button) is not a problem... the harder part would be how the button program signals the appLauncher to kill the proper app. We could do it via websockets but it's awfully overkill. There aren't many RPC mechanisms available on client-side JS, so we'd have to add something to the kiosk browser... the simpler would be a signal handler... if not something like POSIX message queues... things get ugly architecturally at this point, because the appLauncher is just a page and it's technically possible for many instances to exist at the same time (and sometimes they do, like when we nest an appLauncher within another... but also possible in a multi-screen setting, etc.).
Second thought: Passing the pid of the process to kill to the close button program on invocation should be enough... I was thinking that a process can change pid if respawning or forking, but I think that I'd loose control of that process from appLauncher anyway if either happens 🤔
Electron features (can already be applied during window initialization): transparent/frameless, always-on-top, window positioning, window sizing
If these features work indeed depends on the window system, compositor and desktop environment. It will work on our usual openbox/compton setup. On macOS, it is not possible to overlay two apps in fullscreen mode AFAIK. That's why I would probably not add this to the applauncher core, but rather integrate it via a plugin, if possible.
I would close the external executable by killing the PID supplied as an argument somehow. Every message passing approach between the close button and the applauncher seems overkill IMO.
Multi-screen setups are not so easy to deal with, that's true. I would just ignore that for the moment and rely on the exit button positioning option (top-left
, top-right
etc.) as a workaround for the moment.
I thought a bit more about this today and noticed that it might not even be necessary to pass the PID to the plugin. The new browser window that resembles the close button is opened from the plugin that runs within the applauncher that in turn runs in the kiosk-browser respectively electron in general (at least node integration is needed ..). So when the close button is clicked, the plugin will be notified and can directly call back to the applauncher e.g. via the IMAGINARY.*
API or whatever seems reasonable.
Maybe passing the PID is simpler because it yields to better separation between the applauncher and the plugin, but at least there is the possibility of direct communication of the close button and the applauncher.
My suggestion of needing the PID was for the case where the button is not implemented in electron but is the current close button we use, just launched by the browser.
... I agree that if the button is implemented in electron it can be invoked with a callback that does the closing.
Our qclosebutton
takes the command to run as argument, i.e. it starts the program it is supposed to close and is therefore also able to kill it. Same could be done with electron, but with a lot more flexibility because we can utilize full HTML+CSS+JS to implement the close button widget. qclosebutton
just displays a static, semi-transparent image on top of all other windows.
I made some tests today. It works:
The launcher.html
is running in Electron with node integration enabled. When you click "Start app", it will launch an external app and open another BrowserWindow
with closebutton.html
that is always on top, frameless and transparent. launcher.html
will calculate the correct size of the close-button window based on its contents and then position it. The launcher also inserts a hook such that whenever closebutton.html
calls IMAGINARY.AppLauncher.closeApp()
, a callback in launcher.html
is triggered which in this case closes the app and the closebutton.html
window.
closebutton.html
is just:
<html>
<body style="width: 40px; height: 40px; margin: 0px; padding: 0px;">
<a href="javascript:IMAGINARY.AppLauncher.closeApp()">
<img src="button.svg" width="40" height="40"/>
</a>
</body>
</html>
So it is not necessary to expose applauncher (or applaucher plugin) internals to the closebutton.html
or the app. I just emulate the regular public AppLauncher API.
launcher.html
is
<html>
<head>
</head>
<body>
<script>
function open() {
let { remote } = require('electron');
let { BrowserWindow } = remote;
let parentWin = remote.getCurrentWindow();
let appWin = new BrowserWindow({ parent: parentWin, frame: false });
appWin.loadURL('http://localhost:8000/app.html');
let buttonWin = new BrowserWindow({
fullscreen: false,
alwaysOnTop: true,
frame: false,
transparent: true,
resizable: false,
hasShadow: false,
show: false,
});
buttonWin.loadURL('http://localhost:8000/closebutton.html');
buttonWin.on('close', () => console.log('app closed'));
buttonWin.webContents.on('dom-ready', () => {
buttonWin.webContents.executeJavaScript('new Promise((resolve, reject) => window.IMAGINARY = {AppLauncher: {closeApp: resolve}});', true)
.then(result => {
buttonWin.close();
appWin.close();
});
});
buttonWin.once('ready-to-show', () => {
buttonWin.webContents.executeJavaScript('JSON.parse(JSON.stringify({height:document.body.getBoundingClientRect().height,width:document.body.getBoundingClientRect().width}));', true)
.then(rect => {
console.log("closebutton.html window size:" + rect.width + " x " + rect.height);
let parent_rect = parentWin.getContentBounds();
buttonWin.setContentSize(rect.width,rect.height);
buttonWin.setPosition(
parent_rect.x + parent_rect.width - rect.width,
parent_rect.y
);
buttonWin.show();
},err => console.log(err));
});
}
</script>
<br />
<h1><pre>launcher.html</pre></h1>
<a href="javascript:open()">Start app</a>
</body>
On Linux, this should also work in fullscreen mode. It currently also does on macOS Mojave, but only because the "external demo app" in this case is just another BrowserWindow
that belongs to the same main app (kiosk browser).
I tested this on Linux and ran into quite some trouble. If the app window is not in full screen mode, it just works fine. However, full screen windows seem to use the always-on-top
flag as well (this probably also depends on the window manager) such that the app and the close button were fighting for priority.
The only way I could bypass this and have the close button always above the full screen window was to require('x11')
and set X.ChangeWindowAttributes(buttonWin.nativeWindowID, {overrideRedirect: true}))
, something I needed to do for qclosebutton
as well (the Qt::X11BypassWindowManagerHint
part). It is a bit annoying to have this platform specific dependency now, but I don't see any way around it. It also needs to be compiled into kiosk-browser
, which is easy but still not elegant.
Currently, external executable apps need to provide their own way to exit to get back to the applauncher. Most executable apps don't provide such a mechanism because they rely on the window system to deal with that and workarounds like qclosebutton are not ideal and quite annoying to work with.
The back button introduced in #4 could be used as a close/exit button so we could have the same visuals for all kind of apps. I imagine implementing this by opening an additional
BrowserWindow
of the kiosk browser/electron that is semi-transparent, always-on-top and covers only a small part of the screen. This should be implemented by some plugin I guess because it would only work from within an electron based browser with node integration. On the other hand, theexecutable
apps are currently implemented within the applauncher. So I am not sure about the best place to implement this.