Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
092697850d | ||
|
|
422e92dd89 | ||
|
|
a15c0fc91d | ||
|
|
358fa3ca9c | ||
|
|
10722fa217 | ||
|
|
7267fafb97 | ||
|
|
b5e95936dc | ||
|
|
047b38a41b | ||
|
|
a93fbb2ad3 | ||
|
|
0b927a3ef6 | ||
|
|
d35cf27de9 | ||
|
|
19348b4b60 | ||
|
|
ce1eed391d | ||
|
|
fe6fe92656 | ||
|
|
3aea938cfb | ||
|
|
c39428fa42 | ||
|
|
7caa5bfc8a | ||
|
|
5f69961a9f | ||
|
|
f44d998016 | ||
|
|
3ea65a4af8 | ||
|
|
2ad49c3167 | ||
|
|
ef2093a25e | ||
|
|
2737eabd8a | ||
|
|
b8f809c50e | ||
|
|
c3e625a8f6 | ||
|
|
7c15707e3b | ||
|
|
dbd3756793 | ||
|
|
85fe5a910a | ||
|
|
d78088e6af | ||
|
|
bf77d9b2a0 | ||
|
|
f4ca7bc412 | ||
|
|
d0ea377ead | ||
|
|
9a829f72e3 | ||
|
|
482a56c9b9 | ||
|
|
32409983b2 | ||
|
|
fe0667919e | ||
|
|
0037a892cd | ||
|
|
ad22dd3215 | ||
|
|
619179c3c8 | ||
|
|
7e5e669b09 | ||
|
|
437c4ab1c5 |
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
docs/
|
docs/
|
||||||
out/
|
out/
|
||||||
|
build.json
|
||||||
|
|||||||
+16
-14
@@ -27,20 +27,22 @@ If you are running Linux with a graphical user interface (Wayland or X11), you s
|
|||||||
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Official 7" Touch Display 2 (720x1280)](https://www.raspberrypi.com/products/touch-display-2/) | 🟩 |
|
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Official 7" Touch Display 2 (720x1280)](https://www.raspberrypi.com/products/touch-display-2/) | 🟩 |
|
||||||
|
|
||||||
### HDMI
|
### HDMI
|
||||||
| Device | System | Display | Status |
|
| Device | System | Display | Status |
|
||||||
| ------------------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------ |
|
| ------------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------ |
|
||||||
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [LAFVIN 5" Touch Display (800x480)](https://www.amazon.de/gp/product/B0BWJ8YP7S) | 🟨 |
|
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [LAFVIN 5" Touch Display (800x480)](https://www.amazon.de/gp/product/B0BWJ8YP7S) | 🟨 |
|
||||||
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Waveshare 8" CAPLCD (768x1024)](https://www.waveshare.com/wiki/8inch_768x1024_LCD) | 🟨 |
|
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Waveshare 8" CAPLCD (768x1024)](https://www.waveshare.com/wiki/8inch_768x1024_LCD) | 🟨 |
|
||||||
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Waveshare 7" 7EP-CAPLCD (1280x800)](https://www.waveshare.com/7ep-caplcd.htm) | 🟨 |
|
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Waveshare 7" 7EP-CAPLCD (1280x800)](https://www.waveshare.com/7ep-caplcd.htm) | 🟨 |
|
||||||
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Hannspree HT225 21.5" (1920x1080)](https://www.hannspree.eu/product/HT-225-HPB) | 🟨 |
|
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Hannspree HT225 21.5" (1920x1080)](https://www.hannspree.eu/product/HT-225-HPB) | 🟨 |
|
||||||
| Raspberry Pi 400 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [UPerfect Vertical Touch 15.6" (1920x1080)](https://uperfect.com/products/uperfect-y-vertical-monitor-15-6) | 🟨 |
|
| Raspberry Pi 400 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [UPerfect Vertical Touch 15.6" (1920x1080)](https://uperfect.com/products/uperfect-y-vertical-monitor-15-6) | 🟨 |
|
||||||
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [GeeekPi 10.1" Capacitive Touch (1280x800)](https://www.amazon.nl/dp/B0DHV6DZC1) | 🟨 |
|
| Raspberry Pi CM 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Lenovo ThinkCentre 23.8" (1920x1080)](https://psref.lenovo.com/Detail/ThinkCentre_Tiny_In_One_24_Gen_5?M=12NBGAT1EU) | 🟨 |
|
||||||
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Prechen Portable Touch 18.5" FHD (1920x1080)](https://www.amazon.de/dp/B0CT2KLDBQ) | 🟨 |
|
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [GeeekPi 10.1" Capacitive Touch (1280x800)](https://www.amazon.nl/dp/B0DHV6DZC1) | 🟨 |
|
||||||
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Akntzcs Portable Touch 16" HD (1920x1200)](https://www.amazon.com/dp/B0CTGW6MQ6) | 🟨 |
|
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Prechen Portable Touch 18.5" FHD (1920x1080)](https://www.amazon.de/dp/B0CT2KLDBQ) | 🟨 |
|
||||||
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | Generic Non-Touch | 🟨 |
|
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Akntzcs Portable Touch 16" HD (1920x1200)](https://www.amazon.com/dp/B0CTGW6MQ6) | 🟨 |
|
||||||
| Generic PC (x64) | Debian KDE (64-bit), X11 | Generic Non-Touch | 🟧 |
|
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [ELECROW Portable Monitor 10.1" (1280x800)](https://www.amazon.co.uk/dp/B0BHHQLKPY) | 🟨 |
|
||||||
| Generic PC (x64) | Ubuntu GNOME (64-bit), X11 | Generic Non-Touch | 🟧 |
|
| Raspberry Pi 5 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | Generic Non-Touch | 🟨 |
|
||||||
| Generic PC (x64) | Ubuntu GNOME (64-bit), Wayland | Generic Non-Touch | 🟥 |
|
| Generic PC (x64) | Debian KDE (64-bit), Wayland, X11 | Generic Non-Touch | 🟧 |
|
||||||
|
| Generic PC (x64) | Ubuntu GNOME (64-bit), X11 | Generic Non-Touch | 🟧 |
|
||||||
|
| Generic PC (x64) | Ubuntu GNOME (64-bit), Wayland | Generic Non-Touch | 🟥 |
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
In case your hardware is not listed above don't worry, give it a try.
|
In case your hardware is not listed above don't worry, give it a try.
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ Moreover, the device running the **kiosk application** also offers several **Hom
|
|||||||
- [x] Configurable browser zoom and theme support.
|
- [x] Configurable browser zoom and theme support.
|
||||||
- [x] Extended touch screen wake-up functionality.
|
- [x] Extended touch screen wake-up functionality.
|
||||||
- [x] Remote controllable via MQTT.
|
- [x] Remote controllable via MQTT.
|
||||||
- [x] Manage kiosk window status.
|
|
||||||
- [x] Toggle the on-screen keyboard.
|
- [x] Toggle the on-screen keyboard.
|
||||||
- [x] Touch display power and brightness.
|
- [x] Touch display power and brightness.
|
||||||
|
- [x] Manage kiosk window status and zoom.
|
||||||
|
- [x] Show network interfaces and addresses.
|
||||||
|
- [x] List all available system package upgrades.
|
||||||
- [x] Multi-webpage switching and url navigation.
|
- [x] Multi-webpage switching and url navigation.
|
||||||
- [x] Monitor temperature, processor and memory usage.
|
- [x] Monitor temperature, processor and memory usage.
|
||||||
- [x] Execute system reboot and shutdown commands.
|
- [x] Execute system reboot and shutdown commands.
|
||||||
- [x] Show available system package upgrades.
|
|
||||||
|
|
||||||
The kiosk application can be executed with command line arguments to load a **Home Assistant dashboard in fullscreen** mode.
|
The kiosk application can be executed with command line arguments to load a **Home Assistant dashboard in fullscreen** mode.
|
||||||
Additionally, a **MQTT endpoint** can be defined, allowing the application to provide controls and sensors for the Linux device and the connected Touch Display.
|
Additionally, a **MQTT endpoint** can be defined, allowing the application to provide controls and sensors for the Linux device and the connected Touch Display.
|
||||||
@@ -43,7 +44,7 @@ For a comprehensive guide on setting up MQTT with Home Assistant, please refer t
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
On the first run of the application, you may encounter a **setup procedure** and the Home Assistant **login screen**.
|
On the first run of the application, you may encounter a **setup procedure** and the Home Assistant **login screen**.
|
||||||
It's recommended to create a **dedicated user** (local access only) for your kiosk device.
|
It's recommended to create a dedicated Home Assistant user (local access only) for your kiosk device.
|
||||||
|
|
||||||
You might also need a physical keyboard or remote VNC access to input these credentials once.
|
You might also need a physical keyboard or remote VNC access to input these credentials once.
|
||||||
If your hardware is [supported](https://github.com/leukipp/touchkio/blob/main/HARDWARE.md) you may be able to activate the on-screen keyboard using the side [widget](https://github.com/leukipp/touchkio/issues/16).
|
If your hardware is [supported](https://github.com/leukipp/touchkio/blob/main/HARDWARE.md) you may be able to activate the on-screen keyboard using the side [widget](https://github.com/leukipp/touchkio/issues/16).
|
||||||
@@ -56,7 +57,6 @@ bash <(wget -qO- https://raw.githubusercontent.com/leukipp/touchkio/main/install
|
|||||||
```
|
```
|
||||||
Make sure that you run this with your **standard user** and not with root (sudo).
|
Make sure that you run this with your **standard user** and not with root (sudo).
|
||||||
If you are paranoid, or smart, or both, have a look into the [install.sh](https://github.com/leukipp/touchkio/blob/main/install.sh) script before executing external code on your machine.
|
If you are paranoid, or smart, or both, have a look into the [install.sh](https://github.com/leukipp/touchkio/blob/main/install.sh) script before executing external code on your machine.
|
||||||
This command can also be re-run to **update** an existing installation to the latest version.
|
|
||||||
|
|
||||||
The systemd **user service** is enabled by default and the kiosk application should start automatically the next time you boot.
|
The systemd **user service** is enabled by default and the kiosk application should start automatically the next time you boot.
|
||||||
If you need manual control use:
|
If you need manual control use:
|
||||||
@@ -89,6 +89,15 @@ yarn build
|
|||||||
|
|
||||||
</div></details>
|
</div></details>
|
||||||
|
|
||||||
|
#### Update
|
||||||
|
If you have already installed TouchKio and want to upgrade to the latest version, simply install the newer version over the existing one.
|
||||||
|
|
||||||
|
The [install.sh](https://github.com/leukipp/touchkio/blob/main/install.sh) script can also be run to update an existing installation to the **latest version**.
|
||||||
|
The setup procedure can be skipped to use the existing default arguments from the configuration file:
|
||||||
|
```bash
|
||||||
|
bash <(wget -qO- https://raw.githubusercontent.com/leukipp/touchkio/main/install.sh) update
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
Running `touchkio --setup` will prompt you to enter arguments that will be used when the application starts without any specified arguments.
|
Running `touchkio --setup` will prompt you to enter arguments that will be used when the application starts without any specified arguments.
|
||||||
These default arguments are stored in `~/.config/touchkio/Arguments.json`, where they can also be modified.
|
These default arguments are stored in `~/.config/touchkio/Arguments.json`, where they can also be modified.
|
||||||
@@ -109,16 +118,16 @@ For example:
|
|||||||
touchkio --web-url=http://192.168.1.42:8123 --web-theme=light --web-zoom=1.0
|
touchkio --web-url=http://192.168.1.42:8123 --web-theme=light --web-zoom=1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
> <a href="#ref1">↩ </a><a id="foot1"></a>**[1]:** Not necessarily a Home Assistant Url.
|
> <a id="foot1"></a><a href="#ref1">[1]</a>: This doesn't necessarily have to be a Home Assistant Url.
|
||||||
> You can load multiple pages by separating them with a comma:
|
> You can configure multiple pages by separating them with a comma:
|
||||||
> `touchkio --web-url=https://demo.home-assistant.io,https://imvbh.github.io/FlipClock`.
|
> `touchkio --web-url=https://demo.home-assistant.io,https://demo.immichkiosk.app`.
|
||||||
|
|
||||||
In the `~/.config/touchkio/Arguments.json` file:
|
In the `~/.config/touchkio/Arguments.json` file:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"web_url": [
|
"web_url": [
|
||||||
"https://demo.home-assistant.io",
|
"https://demo.home-assistant.io",
|
||||||
"https://imvbh.github.io/FlipClock"
|
"https://demo.immichkiosk.app"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -140,6 +149,29 @@ For example:
|
|||||||
touchkio --web-url=http://192.168.1.42:8123 --mqtt-url=mqtt://192.168.1.42:1883 --mqtt-user=kiosk --mqtt-password=password
|
touchkio --web-url=http://192.168.1.42:8123 --mqtt-url=mqtt://192.168.1.42:1883 --mqtt-user=kiosk --mqtt-password=password
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## User Interface
|
||||||
|
TouchKio provides a simple webview window designed specifically for Touch Displays. Electron apps are known to be resource intensive due to their architecture and the inclusion of a full web browser environment. If you just run the kiosk application without other heavy loads, everything should run smoothly.
|
||||||
|
|
||||||
|
### Touch Controls
|
||||||
|
Additional controls can be found along the right edge of the kiosk application. For more details, you may want to have a closer look here:
|
||||||
|
| Name | Description |
|
||||||
|
| ------------ | -------------------------------------------------------- |
|
||||||
|
| `Widget` | [See #16](https://github.com/leukipp/touchkio/issues/16) |
|
||||||
|
| `Navigation` | [See #45](https://github.com/leukipp/touchkio/issues/45) |
|
||||||
|
| `Pager` | [See #64](https://github.com/leukipp/touchkio/issues/64) |
|
||||||
|
|
||||||
|
|
||||||
|
### Keyboard Shortcuts
|
||||||
|
The application also supports basic shortcuts to enhance navigation and usability for users who prefer or require non-touch input methods:
|
||||||
|
| Name | Description |
|
||||||
|
| --------------------------- | ---------------------------- |
|
||||||
|
| `Control+Left` | Navigate to previous page |
|
||||||
|
| `Control+Right` | Navigate to next page |
|
||||||
|
| `Control+Num_Subtract` | Decrease the page zoom |
|
||||||
|
| `Control+Num_Add` | Increase the page zoom |
|
||||||
|
| `Alt+Left`/`Mouse+Back` | Navigate backward in history |
|
||||||
|
| `Alt+Right`/`Mouse+Forward` | Navigate forward in history |
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
To create your own local build, you first need to install [Node.js](https://pimylifeup.com/raspberry-pi-nodejs) and [Yarn](https://classic.yarnpkg.com/lang/en/docs/install).
|
To create your own local build, you first need to install [Node.js](https://pimylifeup.com/raspberry-pi-nodejs) and [Yarn](https://classic.yarnpkg.com/lang/en/docs/install).
|
||||||
|
|
||||||
@@ -219,35 +251,45 @@ Implementing stronger **security measures** would complicate the setup process a
|
|||||||
When using the kiosk application without initializing the default arguments, you will need to provide them with every command.
|
When using the kiosk application without initializing the default arguments, you will need to provide them with every command.
|
||||||
This means that the password may be stored as plain text in various files, such as `touchkio.service`, `~/.bash_history`, etc.
|
This means that the password may be stored as plain text in various files, such as `touchkio.service`, `~/.bash_history`, etc.
|
||||||
|
|
||||||
To resolve the issue where the first **touch** on a **turned-off screen** triggers a **click event** (which could inadvertently activate Home Assistant actions), a workaround needed to be implemented.
|
To address the problem where the first **touch** on a **turned-off screen** may trigger a **click event** (which could inadvertently activate Home Assistant actions), a workaround needed to be implemented.
|
||||||
When the screen **turns off** the **focus** is removed from the kiosk window.
|
When the screen **turns on/off**, the timestamps of these events are recorded.
|
||||||
This way, the first click only turns the screen on and focuses the window, allowing subsequent clicks to work as expected.
|
If a touch event is detected and the **timestamp** of the **last screen-off** is more recent than the **last screen-on** timestamp, the touch event is discarded and the screen is turned on instead.
|
||||||
|
From that point onward, subsequent touch interactions will function normally as expected.
|
||||||
|
|
||||||
Additionally, to address the problem that scrolling only works with the **web browser scrollbar** on the right, the application is configured to **simulate a touch device** using `Emulation.setEmitTouchEventsForMouse`.
|
Additionally, to address the problem that scrolling only works with the **web browser scrollbar** on the right, the application is configured to **simulate a touch device** using `Emulation.setEmitTouchEventsForMouse`.
|
||||||
This adjustment provides a user experience similar to that of a proper mobile device.
|
This adjustment provides a user experience similar to that of a proper mobile device.
|
||||||
|
|
||||||
Electron apps are known to be **resource intensive** due to their architecture and the inclusion of a full web browser environment. If you just run the kiosk application without other heavy loads, everything should run smoothly.
|
|
||||||
|
|
||||||
</div></details>
|
</div></details>
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
Please have a look into the [hardware](https://github.com/leukipp/touchkio/blob/main/HARDWARE.md) documentation if you ran into any issues.
|
Please review the [hardware](https://github.com/leukipp/touchkio/blob/main/HARDWARE.md) documentation first if you encounter any problems.
|
||||||
|
|
||||||
**Minimal features** are designed to run on any system without issues:
|
**Minimal features** are designed to run on any system without issues:
|
||||||
- A webview kiosk window launched in fullscreen mode and loading the specified `--web-url` website should not cause any problems.
|
- A webview kiosk window launched in fullscreen mode and loading the specified `--web-url` website should not cause any problems.
|
||||||
|
|
||||||
**Extended features** become available when the `--mqtt-*` arguments are provided and the hardware is supported:
|
**Extended features** become available when the `--mqtt-*` arguments are provided and the hardware is supported:
|
||||||
- If your hardware is not fully compatible there should be no crashes, but you may miss some sensors.
|
- If your hardware is not fully compatible there should be no crashes, but you may miss some sensors.
|
||||||
- On some Debian based systems (e.g. Ubuntu), the display status control may only be available when using X11.
|
- On some Debian based systems (e.g. Ubuntu GNOME), the display status control may only be available when using X11.
|
||||||
|
- The following commands are currently implemented to modify the display status.
|
||||||
|
- `wlopm --[on]/[off] *` (Raspberry Pi OS, Wayland)
|
||||||
|
- `kscreen-doctor --dpms [on]/[off]` (Debian KDE, Wayland)
|
||||||
|
- `xset dpms force [on]/[off]` (Generic, X11)
|
||||||
|
|
||||||
**Known Issues** that are by-design or for which there isn't a solution so far:
|
**Known Issues** that are by-design or for which there isn't a solution so far:
|
||||||
- You can use Raspberry Pi's build-in screen blanking functionality, however, if the screen is turned on through Home Assistant after being automatically turned off, it will remain on indefinitely.
|
- You can use Raspberry Pi's build-in [screen blanking](https://www.raspberrypi.com/documentation/computers/configuration.html#screen-blanking-3) functionality, however, if the screen is turned on through Home Assistant after being automatically turned off, it will remain on indefinitely.
|
||||||
- It's recommended to either use the built-in screen blanking feature or implement a Home Assistant automation (e.g. presence detection) to manage the screen status.
|
- It's recommended to either use the built-in screen blanking feature or implement a Home Assistant automation (e.g. presence detection) to manage the screen status.
|
||||||
- When using a Raspberry Pi, the on-screen keyboard doesn't automatically pop-out when entering a text field inside the webview.
|
- When using a Raspberry Pi, the on-screen keyboard doesn't [automatically pop-out](https://github.com/leukipp/touchkio/issues/4) when entering a text field inside the webview.
|
||||||
- As a current [workaround](https://github.com/leukipp/touchkio/issues/4) you can use the side [widget](https://github.com/leukipp/touchkio/issues/16) to toggle the keyboard visibility.
|
- As a current workaround you can use the side [widget](https://github.com/leukipp/touchkio/issues/16) to toggle the keyboard visibility.
|
||||||
|
- Certain commands from the MQTT integration may require elevated privileges to work correctly.
|
||||||
|
- Ensure that your local user has the [necessary permissions](https://github.com/leukipp/touchkio/issues/39) to run `sudo` without being prompted for a password.
|
||||||
- On the terminal you may see some *ERROR:gbm_wrapper.cc* messages.
|
- On the terminal you may see some *ERROR:gbm_wrapper.cc* messages.
|
||||||
- This appears to be a [known issue](https://github.com/electron/electron/issues/42322) that currently lacks a fix, but the webview still works.
|
- This appears to be a [known issue](https://github.com/electron/electron/issues/42322) that currently lacks a fix, but the webview still works.
|
||||||
|
|
||||||
|
For debugging, stop the service and launch `touchkio` directly on the terminal to monitor the log output in real-time.
|
||||||
|
This output is also written into `~/.config/touchkio/logs/main.log` for review.
|
||||||
|
|
||||||
|
If you encounter any problems, please [create an issue](https://github.com/leukipp/touchkio/issues) and include the output of `touchkio --version`, additional information's about your system (such as operating system, hardware specs, etc.) and any relevant log files.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
Thanks to Sebastian ([@pdsccode](https://github.com/pdsccode)) for his contributions on issues and [community](https://community.home-assistant.io/t/kiosk-mode-for-raspberry-pi-with-touch-display/821196) discussions.
|
Thanks to Sebastian ([@pdsccode](https://github.com/pdsccode)) for his contributions on issues and [community](https://community.home-assistant.io/t/kiosk-mode-for-raspberry-pi-with-touch-display/821196) discussions.
|
||||||
|
|
||||||
|
|||||||
+35
-2
@@ -1,4 +1,22 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
const jszip = require("jszip");
|
||||||
|
const crypto = require("crypto");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const date = new Date().toISOString();
|
||||||
|
|
||||||
|
const generateBuildFile = (platform, arch, maker) => {
|
||||||
|
const package = fs.readFileSync(path.join(__dirname, "package.json"), "utf8");
|
||||||
|
const hash = crypto.createHash("sha256").update(package);
|
||||||
|
const data = {
|
||||||
|
id: hash.digest("hex").slice(-6),
|
||||||
|
platform: platform,
|
||||||
|
arch: arch,
|
||||||
|
maker: maker,
|
||||||
|
date: date,
|
||||||
|
};
|
||||||
|
return JSON.stringify(data, null, 2);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
packagerConfig: {
|
packagerConfig: {
|
||||||
@@ -33,10 +51,25 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
hooks: {
|
hooks: {
|
||||||
|
postPackage: async (config, results) => {
|
||||||
|
for (const outputPath of results.outputPaths) {
|
||||||
|
const [name, platform, arch] = path.basename(outputPath).split("-");
|
||||||
|
const buildFile = path.join(outputPath, "resources", "app", "build.json");
|
||||||
|
fs.writeFileSync(buildFile, generateBuildFile(platform, arch, "deb"), { encoding: "utf8" });
|
||||||
|
}
|
||||||
|
},
|
||||||
postMake: async (config, results) => {
|
postMake: async (config, results) => {
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
const artifacts = [];
|
const artifacts = [];
|
||||||
result.artifacts.forEach((artifact) => {
|
for (const artifact of result.artifacts) {
|
||||||
|
if (artifact.includes(".zip")) {
|
||||||
|
const [name, platform, arch] = path.basename(artifact).split("-");
|
||||||
|
const buildFile = path.join(`${name}-${platform}-${arch}`, "resources", "app", "build.json");
|
||||||
|
const options = { type: "nodebuffer", compression: "DEFLATE", compressionOptions: { level: 9 } };
|
||||||
|
const zip = await jszip.loadAsync(fs.readFileSync(artifact));
|
||||||
|
zip.file(buildFile, generateBuildFile(platform, arch, "zip"));
|
||||||
|
fs.writeFileSync(artifact, await zip.generateAsync(options));
|
||||||
|
}
|
||||||
if (artifact.includes("amd64")) {
|
if (artifact.includes("amd64")) {
|
||||||
const renamed = artifact.replace("amd64", "x64");
|
const renamed = artifact.replace("amd64", "x64");
|
||||||
fs.renameSync(artifact, renamed);
|
fs.renameSync(artifact, renamed);
|
||||||
@@ -44,7 +77,7 @@ module.exports = {
|
|||||||
} else {
|
} else {
|
||||||
artifacts.push(artifact);
|
artifacts.push(artifact);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
result.artifacts = artifacts;
|
result.artifacts = artifacts;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+59
-6
@@ -46,11 +46,19 @@
|
|||||||
li.dynamic {
|
li.dynamic {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
|
li.separator {
|
||||||
|
height: 32px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
li.separator:has(+ li.hidden) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
li.hidden {
|
li.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
li.disabled {
|
li.disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
@@ -62,12 +70,13 @@
|
|||||||
input {
|
input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin: 0 10px;
|
margin: 0 5px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
cursor: text;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
background-color: var(--border-color);
|
background-color: var(--border-color);
|
||||||
@@ -91,7 +100,28 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<button id="pager">
|
<button id="refresh">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<title>refresh-circle</title>
|
||||||
|
<path
|
||||||
|
d="M12 2A10 10 0 1 0 22 12A10 10 0 0 0 12 2M18 11H13L14.81 9.19A3.94 3.94 0 0 0 12 8A4 4 0 1 0 15.86 13H17.91A6 6 0 1 1 12 6A5.91 5.91 0 0 1 16.22 7.78L18 6Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="separator"></li>
|
||||||
|
<li>
|
||||||
|
<button id="previous">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<title>page-previous</title>
|
||||||
|
<path
|
||||||
|
d="M4,21H19A2,2 0 0,0 21,19V13H17V15L13,12L17,9V11H21V5A2,2 0 0,0 19,3H4A2,2 0 0,0 2,5V19A2,2 0 0,0 4,21M4,15H8V17H4V15M4,11H11V13H4V11M4,7H11V9H4V7M21,11H24V13H21V11Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button id="next">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<title>page-next</title>
|
<title>page-next</title>
|
||||||
<path
|
<path
|
||||||
@@ -100,15 +130,17 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="separator"></li>
|
||||||
<li class="dynamic">
|
<li class="dynamic">
|
||||||
<input id="url" type="text" spellcheck="false" />
|
<input id="url" type="text" spellcheck="false" />
|
||||||
</li>
|
</li>
|
||||||
|
<li class="separator"></li>
|
||||||
<li>
|
<li>
|
||||||
<button id="backward">
|
<button id="backward">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<title>arrow-left-circle</title>
|
<title>history</title>
|
||||||
<path
|
<path
|
||||||
d="M2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12M18,11H10L13.5,7.5L12.08,6.08L6.16,12L12.08,17.92L13.5,16.5L10,13H18V11Z"
|
d="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
@@ -116,9 +148,30 @@
|
|||||||
<li>
|
<li>
|
||||||
<button id="forward">
|
<button id="forward">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<title>arrow-right-circle</title>
|
<title>update</title>
|
||||||
<path
|
<path
|
||||||
d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M6,13H14L10.5,16.5L11.92,17.92L17.84,12L11.92,6.08L10.5,7.5L14,11H6V13Z"
|
d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="separator"></li>
|
||||||
|
<li>
|
||||||
|
<button id="minus">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<title>magnify-minus</title>
|
||||||
|
<path
|
||||||
|
d="M9,2A7,7 0 0,1 16,9C16,10.57 15.5,12 14.61,13.19L15.41,14H16L22,20L20,22L14,16V15.41L13.19,14.61C12,15.5 10.57,16 9,16A7,7 0 0,1 2,9A7,7 0 0,1 9,2M5,8V10H13V8H5Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button id="plus">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<title>magnify-plus</title>
|
||||||
|
<path
|
||||||
|
d="M9,2A7,7 0 0,1 16,9C16,10.57 15.5,12 14.61,13.19L15.41,14H16L22,20L20,22L14,16V15.41L13.19,14.61C12,15.5 10.57,16 9,16A7,7 0 0,1 2,9A7,7 0 0,1 9,2M8,5V8H5V10H8V13H10V10H13V8H10V5H8Z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
[data-theme="hidden"] {
|
||||||
|
--primary-color: rgba(0, 0, 0, 0);
|
||||||
|
--border-color: rgba(0, 0, 0, 0);
|
||||||
|
--background-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
list-style-type: none;
|
||||||
|
background: var(--background-color);
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100vw;
|
||||||
|
height: 50vh;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
#previous {
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
#next {
|
||||||
|
cursor: e-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<menu>
|
||||||
|
<li>
|
||||||
|
<button id="previous"></button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button id="next"></button>
|
||||||
|
</li>
|
||||||
|
</menu>
|
||||||
|
<script>
|
||||||
|
const { ipcRenderer } = require("electron");
|
||||||
|
|
||||||
|
const registerSendEvents = () => {
|
||||||
|
document.querySelectorAll("button").forEach((button) => {
|
||||||
|
button.addEventListener("click", (e) => {
|
||||||
|
ipcRenderer.send("button-click", { id: e.target.id });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerReceiveEvents = () => {
|
||||||
|
ipcRenderer.on("data-theme", (e, data) => {
|
||||||
|
document.documentElement.setAttribute("data-theme", data.theme);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
registerSendEvents();
|
||||||
|
registerReceiveEvents();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 78 KiB |
@@ -5,9 +5,13 @@ const readline = require("readline/promises");
|
|||||||
const integration = require("./js/integration");
|
const integration = require("./js/integration");
|
||||||
const hardware = require("./js/hardware");
|
const hardware = require("./js/hardware");
|
||||||
const webview = require("./js/webview");
|
const webview = require("./js/webview");
|
||||||
|
const log = require("electron-log");
|
||||||
const { app } = require("electron");
|
const { app } = require("electron");
|
||||||
|
const Events = require("events");
|
||||||
|
|
||||||
|
global.APP = global.APP || {};
|
||||||
global.ARGS = global.ARGS || {};
|
global.ARGS = global.ARGS || {};
|
||||||
|
global.EVENTS = global.EVENTS || new Events();
|
||||||
|
|
||||||
if (!process.env.DISPLAY) {
|
if (!process.env.DISPLAY) {
|
||||||
console.error(`\n$DISPLAY variable not set to run the GUI application, are you connected via SSH?\n`);
|
console.error(`\n$DISPLAY variable not set to run the GUI application, are you connected via SSH?\n`);
|
||||||
@@ -23,20 +27,90 @@ if (!process.env.DISPLAY) {
|
|||||||
* initialization tasks.
|
* initialization tasks.
|
||||||
*/
|
*/
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
const lock = app.requestSingleInstanceLock();
|
if (!(await initApp()) || !(await initArgs()) || !(await initLog())) {
|
||||||
if (!lock) {
|
return;
|
||||||
console.error("TouchKio is already running");
|
}
|
||||||
|
|
||||||
|
// Show used arguments
|
||||||
|
const args = Object.assign({}, ARGS);
|
||||||
|
if ("mqtt_password" in args) {
|
||||||
|
args.mqtt_password = "*".repeat((args.mqtt_password || "").length);
|
||||||
|
}
|
||||||
|
console.log(`Arguments: ${JSON.stringify(args, null, 2)}`);
|
||||||
|
|
||||||
|
// Chained init functions
|
||||||
|
const chained = [webview.init, hardware.init, integration.init];
|
||||||
|
for (const init of chained) {
|
||||||
|
if (!(await init())) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the global app object.
|
||||||
|
*
|
||||||
|
* @returns {bool} Returns true if the initialization was successful.
|
||||||
|
*/
|
||||||
|
const initApp = async () => {
|
||||||
|
const packageJsonPath = path.join(app.getAppPath(), "package.json");
|
||||||
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
||||||
|
|
||||||
|
const buildJsonPath = path.join(app.getAppPath(), "build.json");
|
||||||
|
const buildFileExists = fs.existsSync(buildJsonPath);
|
||||||
|
|
||||||
|
// Set required app infos
|
||||||
|
APP.name = app.getName();
|
||||||
|
APP.title = packageJson.title;
|
||||||
|
APP.version = app.getVersion();
|
||||||
|
APP.path = app.getAppPath();
|
||||||
|
APP.config = app.getPath("userData");
|
||||||
|
APP.log = path.join(app.getPath("logs"), "main.log");
|
||||||
|
|
||||||
|
// Set additional update infos
|
||||||
|
APP.homepage = `https://github.com/${packageJson.author}/${packageJson.name}`;
|
||||||
|
APP.releases = {
|
||||||
|
url: `https://api.github.com/repos/${packageJson.author}/${packageJson.name}/releases`,
|
||||||
|
latest: null,
|
||||||
|
};
|
||||||
|
APP.scripts = {
|
||||||
|
install: `https://raw.githubusercontent.com/${packageJson.author}/${packageJson.name}/main/install.sh`,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set additional build infos
|
||||||
|
APP.build = {};
|
||||||
|
if (buildFileExists) {
|
||||||
|
APP.build = JSON.parse(fs.readFileSync(buildJsonPath, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request single app instance lock
|
||||||
|
if (!app.requestSingleInstanceLock()) {
|
||||||
|
console.error(`${APP.title} is already running`);
|
||||||
return app.quit();
|
return app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the global args object.
|
||||||
|
*
|
||||||
|
* @returns {bool} Returns true if the initialization was successful.
|
||||||
|
*/
|
||||||
|
const initArgs = async () => {
|
||||||
let args = parseArgs(process);
|
let args = parseArgs(process);
|
||||||
let argsProvided = !!Object.keys(args).length;
|
let argsProvided = !!Object.keys(args).length;
|
||||||
|
|
||||||
let argsFilePath = path.join(app.getPath("userData"), "Arguments.json");
|
let argsFilePath = path.join(APP.config, "Arguments.json");
|
||||||
let argsFileExists = fs.existsSync(argsFilePath);
|
let argsFileExists = fs.existsSync(argsFilePath);
|
||||||
|
|
||||||
// Show version and release info
|
// Show version and release info
|
||||||
if ("help" in args || "version" in args) {
|
if ("help" in args || "version" in args) {
|
||||||
console.log(`${app.getName()}-v${app.getVersion()}, https://github.com/leukipp/touchkio`);
|
let build = "";
|
||||||
|
if (APP.build.id) {
|
||||||
|
build = ` (${APP.build.id}), built on ${APP.build.date} (${APP.build.platform}-${APP.build.arch}-${APP.build.maker})`;
|
||||||
|
}
|
||||||
|
console.log(`${APP.name}-v${APP.version}${build}\n${APP.homepage}`);
|
||||||
return app.quit();
|
return app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,24 +125,44 @@ app.whenReady().then(async () => {
|
|||||||
args = readArgs(argsFilePath);
|
args = readArgs(argsFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check arguments object
|
||||||
|
if (!Object.keys(args).length) {
|
||||||
|
console.error(`No arguments provided`);
|
||||||
|
return app.quit();
|
||||||
|
}
|
||||||
|
|
||||||
// Split url arguments
|
// Split url arguments
|
||||||
args.web_url = args.web_url || [];
|
args.web_url = args.web_url || [];
|
||||||
if (!Array.isArray(args.web_url)) {
|
if (!Array.isArray(args.web_url)) {
|
||||||
args.web_url = args.web_url.split(",").map((url) => url.trim());
|
args.web_url = args.web_url.split(",").map((url) => url.trim());
|
||||||
}
|
}
|
||||||
global.ARGS = args;
|
ARGS = args;
|
||||||
|
|
||||||
// Show used arguments
|
return true;
|
||||||
console.log(`Arguments: ${JSON.stringify(global.ARGS, null, 2)}`);
|
};
|
||||||
|
|
||||||
// Chained init functions
|
/**
|
||||||
const chained = [webview.init, hardware.init, integration.init];
|
* Initializes the global log object.
|
||||||
for (const init of chained) {
|
*
|
||||||
if (!(await init())) {
|
* @returns {bool} Returns true if the initialization was successful.
|
||||||
break;
|
*/
|
||||||
|
const initLog = async () => {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(APP.log)) {
|
||||||
|
fs.unlinkSync(APP.log);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to delete log file:", error.message);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// Overwrite console log settings
|
||||||
|
log.transports.file.resolvePathFn = () => {
|
||||||
|
return APP.log;
|
||||||
|
};
|
||||||
|
Object.assign(console, log.functions);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses command-line arguments from the given process object.
|
* Parses command-line arguments from the given process object.
|
||||||
@@ -155,34 +249,41 @@ const promptArgs = async (proc) => {
|
|||||||
// Prompt questions and wait for the answers
|
// Prompt questions and wait for the answers
|
||||||
let args = {};
|
let args = {};
|
||||||
let ignore = [];
|
let ignore = [];
|
||||||
for (const { key, question, fallback } of prompts) {
|
try {
|
||||||
if (key === "mqtt") {
|
for (const { key, question, fallback } of prompts) {
|
||||||
const prompt = `${question} (${fallback}): `;
|
if (key === "mqtt") {
|
||||||
const answer = await read.question(prompt);
|
const prompt = `${question} (${fallback}): `;
|
||||||
const value = (answer.trim() || fallback.match(/[YN]/)[0]).toLowerCase();
|
const answer = await read.question(prompt);
|
||||||
if (!["y", "yes"].includes(value)) {
|
const value = (answer.trim() || fallback.match(/[YN]/)[0]).toLowerCase();
|
||||||
ignore = ignore.concat(["mqtt_url", "mqtt_user", "mqtt_password", "mqtt_discovery"]);
|
if (!["y", "yes"].includes(value)) {
|
||||||
}
|
ignore = ignore.concat(["mqtt_url", "mqtt_user", "mqtt_password", "mqtt_discovery"]);
|
||||||
} else if (key === "check") {
|
}
|
||||||
const json = JSON.stringify(args, null, 2);
|
} else if (key === "check") {
|
||||||
const prompt = `${question}\n${json}\n(${fallback}): `;
|
const json = JSON.stringify(args, null, 2);
|
||||||
const answer = await read.question(prompt);
|
const prompt = `${question}\n${json}\n(${fallback}): `;
|
||||||
const value = (answer.trim() || fallback.match(/[YN]/)[0]).toLowerCase();
|
const answer = await read.question(prompt);
|
||||||
if (!["y", "yes"].includes(value)) {
|
const value = (answer.trim() || fallback.match(/[YN]/)[0]).toLowerCase();
|
||||||
args = {};
|
if (!["y", "yes"].includes(value)) {
|
||||||
}
|
args = {};
|
||||||
} else if (!ignore.includes(key)) {
|
}
|
||||||
const prompt = `${question} (${fallback}): `;
|
} else if (!ignore.includes(key)) {
|
||||||
const answer = await read.question(prompt);
|
const prompt = `${question} (${fallback}): `;
|
||||||
const value = answer.trim() || fallback;
|
const answer = await read.question(prompt);
|
||||||
if (key === "web_url") {
|
const value = answer.trim() || fallback;
|
||||||
args[key] = value.split(",").map((v) => v.trim());
|
if (key === "web_url") {
|
||||||
} else {
|
args[key] = value.split(",").map((v) => v.trim());
|
||||||
args[key] = value;
|
} else {
|
||||||
|
args[key] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`\n${error.message}`);
|
||||||
|
args = {};
|
||||||
|
app.quit();
|
||||||
|
} finally {
|
||||||
|
read.close();
|
||||||
}
|
}
|
||||||
read.close();
|
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
};
|
};
|
||||||
@@ -194,11 +295,15 @@ const promptArgs = async (proc) => {
|
|||||||
* @param {Object} args - The arguments object.
|
* @param {Object} args - The arguments object.
|
||||||
*/
|
*/
|
||||||
const writeArgs = (path, args) => {
|
const writeArgs = (path, args) => {
|
||||||
const argc = Object.assign({}, args);
|
try {
|
||||||
if ("mqtt_password" in argc) {
|
const argc = Object.assign({}, args);
|
||||||
argc.mqtt_password = encrypt(argc.mqtt_password);
|
if ("mqtt_password" in argc) {
|
||||||
|
argc.mqtt_password = encrypt(argc.mqtt_password);
|
||||||
|
}
|
||||||
|
fs.writeFileSync(path, JSON.stringify(argc, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to write ${path}:`, error.message);
|
||||||
}
|
}
|
||||||
fs.writeFileSync(path, JSON.stringify(argc, null, 2));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -208,11 +313,16 @@ const writeArgs = (path, args) => {
|
|||||||
* @returns {Object} The arguments object.
|
* @returns {Object} The arguments object.
|
||||||
*/
|
*/
|
||||||
const readArgs = (path) => {
|
const readArgs = (path) => {
|
||||||
const args = JSON.parse(fs.readFileSync(path));
|
try {
|
||||||
if ("mqtt_password" in args) {
|
const args = JSON.parse(fs.readFileSync(path, "utf8"));
|
||||||
args.mqtt_password = decrypt(args.mqtt_password);
|
if ("mqtt_password" in args) {
|
||||||
|
args.mqtt_password = decrypt(args.mqtt_password);
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to parse ${path}:`, error.message);
|
||||||
}
|
}
|
||||||
return args;
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -223,7 +333,7 @@ const readArgs = (path) => {
|
|||||||
*/
|
*/
|
||||||
const encrypt = (value) => {
|
const encrypt = (value) => {
|
||||||
const iv = crypto.randomBytes(16);
|
const iv = crypto.randomBytes(16);
|
||||||
const key = crypto.scryptSync(hardware.getMachineId(), app.getName(), 32);
|
const key = crypto.scryptSync(hardware.getMachineId(), APP.name, 32);
|
||||||
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
|
||||||
let encrypted = cipher.update(value, "utf8", "hex");
|
let encrypted = cipher.update(value, "utf8", "hex");
|
||||||
encrypted += cipher.final("hex");
|
encrypted += cipher.final("hex");
|
||||||
@@ -239,7 +349,7 @@ const encrypt = (value) => {
|
|||||||
const decrypt = (value) => {
|
const decrypt = (value) => {
|
||||||
const p = Buffer.from(value, "base64").toString("utf8").split(":");
|
const p = Buffer.from(value, "base64").toString("utf8").split(":");
|
||||||
const iv = Buffer.from(p.shift(), "hex");
|
const iv = Buffer.from(p.shift(), "hex");
|
||||||
const key = crypto.scryptSync(hardware.getMachineId(), app.getName(), 32);
|
const key = crypto.scryptSync(hardware.getMachineId(), APP.name, 32);
|
||||||
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
|
const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
|
||||||
const buffer = Buffer.from(p.join(":"), "hex");
|
const buffer = Buffer.from(p.join(":"), "hex");
|
||||||
let decrypted = decipher.update(buffer, "binary", "utf8");
|
let decrypted = decipher.update(buffer, "binary", "utf8");
|
||||||
|
|||||||
+55
-42
@@ -1,5 +1,13 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Read arguments
|
||||||
|
ARG_UPDATE=false
|
||||||
|
for arg in "$@"; do
|
||||||
|
if [ "$arg" = "update" ]; then
|
||||||
|
ARG_UPDATE=true
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# Determine system architecture
|
# Determine system architecture
|
||||||
echo -e "Determining system architecture..."
|
echo -e "Determining system architecture..."
|
||||||
|
|
||||||
@@ -12,98 +20,103 @@ case "$(uname -m)" in
|
|||||||
ARCH="x64"
|
ARCH="x64"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Architecture $(uname -m) running $BITS-bit operating system is not supported."
|
{ echo "Architecture $(uname -m) running $BITS-bit operating system is not supported."; exit 1; }
|
||||||
exit 1
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if [ "$BITS" -ne 64 ]; then
|
[ "$BITS" -eq 64 ] || { echo "Architecture $ARCH running $BITS-bit operating system is not supported."; exit 1; }
|
||||||
echo "Architecture $ARCH running $BITS-bit operating system is not supported."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Architecture $ARCH running $BITS-bit operating system is supported."
|
echo "Architecture $ARCH running $BITS-bit operating system is supported."
|
||||||
|
|
||||||
# Download the latest .deb package
|
# Download the latest .deb package
|
||||||
echo -e "\nDownloading the latest release..."
|
echo -e "\nDownloading the latest release..."
|
||||||
|
|
||||||
|
TMP_DIR=$(mktemp -d)
|
||||||
DEB_URL=$(wget -qO- https://api.github.com/repos/leukipp/touchkio/releases/latest | \
|
DEB_URL=$(wget -qO- https://api.github.com/repos/leukipp/touchkio/releases/latest | \
|
||||||
grep -o "\"browser_download_url\": \"[^\"]*_${ARCH}\.deb\"" | \
|
grep -o "\"browser_download_url\": \"[^\"]*_${ARCH}\.deb\"" | \
|
||||||
sed 's/"browser_download_url": "//;s/"//g')
|
sed 's/"browser_download_url": "//;s/"//g')
|
||||||
|
|
||||||
TMP_DIR=$(mktemp -d)
|
|
||||||
DEB_PATH="${TMP_DIR}/$(basename "$DEB_URL")"
|
DEB_PATH="${TMP_DIR}/$(basename "$DEB_URL")"
|
||||||
chmod 755 "$TMP_DIR"
|
chmod 755 "$TMP_DIR"
|
||||||
|
|
||||||
if [ -z "$DEB_URL" ]; then
|
[ -z "$DEB_URL" ] && { echo "Download url for .deb file not found."; exit 1; }
|
||||||
echo "Download url for .deb file not found."
|
wget --show-progress -q -O "$DEB_PATH" "$DEB_URL" || { echo "Failed to download the .deb file."; exit 1; }
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! wget --show-progress -q -O "$DEB_PATH" "$DEB_URL"; then
|
|
||||||
echo "Failed to download the .deb file."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Install the latest .deb package
|
# Install the latest .deb package
|
||||||
echo -e "\nInstalling the latest release..."
|
echo -e "\nInstalling the latest release..."
|
||||||
|
|
||||||
if ! command -v apt &> /dev/null; then
|
command -v apt &> /dev/null || { echo "Package manager apt was not found."; exit 1; }
|
||||||
echo "Package manager apt was not found."
|
sudo apt install -y "$DEB_PATH" || { echo "Installation of .deb file failed."; exit 1; }
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! sudo apt install "$DEB_PATH"; then
|
|
||||||
echo "Installation of .deb file failed."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create the systemd user service
|
# Create the systemd user service
|
||||||
echo -e "\nCreating systemd user service..."
|
echo -e "\nCreating systemd user service..."
|
||||||
|
|
||||||
SERVICE_FILE="$HOME/.config/systemd/user/touchkio.service"
|
SERVICE_NAME="touchkio.service"
|
||||||
mkdir -p "$(dirname "$SERVICE_FILE")" || { echo "Failed to create directory for $SERVICE_FILE"; exit 1; }
|
SERVICE_FILE="$HOME/.config/systemd/user/$SERVICE_NAME"
|
||||||
|
mkdir -p "$(dirname "$SERVICE_FILE")" || { echo "Failed to create directory for $SERVICE_FILE."; exit 1; }
|
||||||
|
|
||||||
bash -c "cat << EOF > \"$SERVICE_FILE\"
|
SERVICE_CONTENT="[Unit]
|
||||||
[Unit]
|
|
||||||
Description=TouchKio
|
Description=TouchKio
|
||||||
After=graphical.target
|
After=graphical.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/usr/bin/touchkio
|
ExecStart=/usr/bin/touchkio
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5s
|
RestartSec=5s
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=default.target
|
WantedBy=default.target"
|
||||||
EOF"
|
|
||||||
|
|
||||||
systemctl --user enable "$(basename "$SERVICE_FILE")" || { echo "Failed to enable service $SERVICE_FILE"; exit 1; }
|
if $ARG_UPDATE; then
|
||||||
echo "Service $SERVICE_FILE enabled"
|
if systemctl --user --quiet is-active "${SERVICE_NAME}"; then
|
||||||
|
systemctl --user restart "${SERVICE_NAME}"
|
||||||
|
echo "Existing $SERVICE_NAME restarted."
|
||||||
|
else
|
||||||
|
echo "Existing $SERVICE_NAME not running, start touchkio manually."
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERVICE_CREATE=true
|
||||||
|
if [ -f "$SERVICE_FILE" ]; then
|
||||||
|
read -p "Service $SERVICE_FILE exists, overwrite? (y/N) " overwrite
|
||||||
|
[[ ${overwrite:-n} == [Yy]* ]] || SERVICE_CREATE=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $SERVICE_CREATE; then
|
||||||
|
echo "$SERVICE_CONTENT" > "$SERVICE_FILE" || { echo "Failed to write to $SERVICE_FILE."; exit 1; }
|
||||||
|
systemctl --user enable "$(basename "$SERVICE_FILE")" || { echo "Failed to enable service $SERVICE_FILE."; exit 1; }
|
||||||
|
echo "Service $SERVICE_FILE enabled."
|
||||||
|
else
|
||||||
|
echo "Service $SERVICE_FILE not created."
|
||||||
|
fi
|
||||||
|
|
||||||
# Export display variables
|
# Export display variables
|
||||||
echo -e "\nExporting display variables..."
|
echo -e "\nExporting display variables..."
|
||||||
|
|
||||||
if [ -z "$DISPLAY" ]; then
|
if [ -z "$DISPLAY" ]; then
|
||||||
export DISPLAY=":0"
|
export DISPLAY=":0"
|
||||||
echo "DISPLAY was not set, defaulting to \"$DISPLAY\""
|
echo "DISPLAY was not set, defaulting to \"$DISPLAY\"."
|
||||||
else
|
else
|
||||||
echo "DISPLAY is set to \"$DISPLAY\""
|
echo "DISPLAY is set to \"$DISPLAY\"."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$WAYLAND_DISPLAY" ]; then
|
if [ -z "$WAYLAND_DISPLAY" ]; then
|
||||||
export WAYLAND_DISPLAY="wayland-0"
|
export WAYLAND_DISPLAY="wayland-0"
|
||||||
echo "WAYLAND_DISPLAY was not set, defaulting to \"$WAYLAND_DISPLAY\""
|
echo "WAYLAND_DISPLAY was not set, defaulting to \"$WAYLAND_DISPLAY\"."
|
||||||
else
|
else
|
||||||
echo "WAYLAND_DISPLAY is set to \"$WAYLAND_DISPLAY\""
|
echo "WAYLAND_DISPLAY is set to \"$WAYLAND_DISPLAY\"."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start the setup mode
|
# Start the setup mode
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Start touchkio setup? (Y/n) " answer
|
read -p "Start touchkio setup? (Y/n) " setup
|
||||||
if [[ ${answer:-y} == [Yy]* ]]; then
|
|
||||||
|
if [[ ${setup:-y} == [Yy]* ]]; then
|
||||||
echo "/usr/bin/touchkio --setup"
|
echo "/usr/bin/touchkio --setup"
|
||||||
/usr/bin/touchkio --setup
|
/usr/bin/touchkio --setup
|
||||||
else
|
else
|
||||||
echo "/usr/bin/touchkio"
|
echo "/usr/bin/touchkio"
|
||||||
/usr/bin/touchkio
|
/usr/bin/touchkio
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|||||||
+260
-85
@@ -6,9 +6,8 @@ const cpr = require("child_process");
|
|||||||
|
|
||||||
global.HARDWARE = global.HARDWARE || {
|
global.HARDWARE = global.HARDWARE || {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
status: null,
|
|
||||||
session: {},
|
|
||||||
support: {},
|
support: {},
|
||||||
|
session: {},
|
||||||
battery: {
|
battery: {
|
||||||
level: {
|
level: {
|
||||||
path: null,
|
path: null,
|
||||||
@@ -16,19 +15,18 @@ global.HARDWARE = global.HARDWARE || {
|
|||||||
},
|
},
|
||||||
display: {
|
display: {
|
||||||
status: {
|
status: {
|
||||||
path: null,
|
|
||||||
value: null,
|
value: null,
|
||||||
|
path: null,
|
||||||
|
command: null,
|
||||||
},
|
},
|
||||||
brightness: {
|
brightness: {
|
||||||
path: null,
|
|
||||||
value: null,
|
value: null,
|
||||||
|
path: null,
|
||||||
max: null,
|
max: null,
|
||||||
},
|
},
|
||||||
notifiers: [],
|
|
||||||
},
|
},
|
||||||
keyboard: {
|
keyboard: {
|
||||||
visible: null,
|
visible: null,
|
||||||
notifiers: [],
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,51 +36,68 @@ global.HARDWARE = global.HARDWARE || {
|
|||||||
* @returns {bool} Returns true if the initialization was successful.
|
* @returns {bool} Returns true if the initialization was successful.
|
||||||
*/
|
*/
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
if (!compatibleDevice()) {
|
if (!compatibleSystem()) {
|
||||||
console.warn("Device not supported");
|
console.error("Operating system is not supported");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init globals
|
// Init globals
|
||||||
HARDWARE.session.user = sessionUser();
|
HARDWARE.session.user = sessionUser();
|
||||||
HARDWARE.session.type = sessionType();
|
HARDWARE.session.type = sessionType();
|
||||||
|
HARDWARE.session.desktop = sessionDesktop();
|
||||||
HARDWARE.battery.level.path = getBatteryLevelPath();
|
HARDWARE.battery.level.path = getBatteryLevelPath();
|
||||||
HARDWARE.support.batteryLevel = HARDWARE.battery.level.path !== null;
|
|
||||||
HARDWARE.display.status.path = getDisplayStatusPath();
|
HARDWARE.display.status.path = getDisplayStatusPath();
|
||||||
HARDWARE.support.displayStatus = HARDWARE.display.status.path !== null;
|
HARDWARE.display.status.command = getDisplayStatusCommand();
|
||||||
HARDWARE.display.brightness.path = getDisplayBrightnessPath();
|
HARDWARE.display.brightness.path = getDisplayBrightnessPath();
|
||||||
HARDWARE.support.displayBrightness = HARDWARE.display.brightness.path !== null;
|
|
||||||
HARDWARE.display.brightness.max = getDisplayBrightnessMax();
|
HARDWARE.display.brightness.max = getDisplayBrightnessMax();
|
||||||
HARDWARE.support.keyboardVisibility = processRuns("squeekboard");
|
HARDWARE.support = checkSupport();
|
||||||
HARDWARE.initialized = true;
|
HARDWARE.initialized = true;
|
||||||
HARDWARE.status = "Supported";
|
|
||||||
|
|
||||||
// Show supported features
|
// Show supported features
|
||||||
|
process.stdout.write("\n");
|
||||||
console.log(`Supported: ${JSON.stringify(HARDWARE.support, null, 2)}`);
|
console.log(`Supported: ${JSON.stringify(HARDWARE.support, null, 2)}`);
|
||||||
|
|
||||||
// Show session infos
|
// Show session infos
|
||||||
console.log("\nUser:", HARDWARE.session.user);
|
process.stdout.write("\n");
|
||||||
|
console.log("User:", HARDWARE.session.user);
|
||||||
console.log("Session:", HARDWARE.session.type);
|
console.log("Session:", HARDWARE.session.type);
|
||||||
|
console.log("Desktop:", HARDWARE.session.desktop);
|
||||||
|
|
||||||
// Show device infos
|
// Show device infos
|
||||||
console.log("\nModel:", getModel());
|
process.stdout.write("\n");
|
||||||
|
console.log("Model:", getModel());
|
||||||
console.log("Vendor:", getVendor());
|
console.log("Vendor:", getVendor());
|
||||||
console.log("Serial Number:", getSerialNumber());
|
console.log("Serial Number:", getSerialNumber());
|
||||||
|
console.log("Network Addresses:", getNetworkAddresses());
|
||||||
console.log("Host Name:", getHostName());
|
console.log("Host Name:", getHostName());
|
||||||
|
|
||||||
// Show system infos
|
// Show system infos
|
||||||
console.log("\nUp Time:", getUpTime());
|
process.stdout.write("\n");
|
||||||
|
console.log("Up Time:", getUpTime());
|
||||||
console.log("Memory Size:", getMemorySize());
|
console.log("Memory Size:", getMemorySize());
|
||||||
console.log("Memory Usage:", getMemoryUsage());
|
console.log("Memory Usage:", getMemoryUsage());
|
||||||
console.log("Processor Usage:", getProcessorUsage());
|
console.log("Processor Usage:", getProcessorUsage());
|
||||||
console.log("Processor Temperature:", getProcessorTemperature());
|
console.log("Processor Temperature:", getProcessorTemperature());
|
||||||
|
|
||||||
// Show hardware infos
|
// Show hardware infos
|
||||||
console.log(`\nBattery Level [${HARDWARE.battery.level.path}]:`, getBatteryLevel());
|
process.stdout.write("\n");
|
||||||
console.log(`Display Status [${HARDWARE.display.status.path}]:`, getDisplayStatus());
|
const unsupported = "unsupported";
|
||||||
console.log(`Display Brightness [${HARDWARE.display.brightness.path}]:`, getDisplayBrightness(), "\n");
|
console.log(`Battery Level [${HARDWARE.battery.level.path || unsupported}]:`, `${getBatteryLevel() || unsupported}`);
|
||||||
|
console.log(
|
||||||
|
`Display Status [${HARDWARE.display.status.path || unsupported}]:`,
|
||||||
|
`${getDisplayStatus() || unsupported} (${HARDWARE.display.status.command || unsupported})`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Display Brightness [${HARDWARE.display.brightness.path || unsupported}]:`,
|
||||||
|
`${getDisplayBrightness() || unsupported}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`Keyboard Visibility [${HARDWARE.support.keyboardVisibility ? "squeekboard" : unsupported}]:`,
|
||||||
|
`${getKeyboardVisibility() || unsupported}`,
|
||||||
|
);
|
||||||
|
|
||||||
// Check for keyboard visibility
|
// Check for keyboard visibility
|
||||||
|
process.stdout.write("\n");
|
||||||
setKeyboardVisibility("OFF", (reply, error) => {
|
setKeyboardVisibility("OFF", (reply, error) => {
|
||||||
if (!reply || error) {
|
if (!reply || error) {
|
||||||
return;
|
return;
|
||||||
@@ -92,14 +107,14 @@ const init = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
HARDWARE.keyboard.visibility = property.Visible === "true";
|
HARDWARE.keyboard.visibility = property.Visible === "true";
|
||||||
HARDWARE.keyboard.notifiers.forEach((notifier) => {
|
EVENTS.emit("updateKeyboard");
|
||||||
notifier();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for display changes
|
// Check for display changes
|
||||||
interval(update, 1000);
|
setDisplayStatus("ON", () => {
|
||||||
|
interval(update, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -118,9 +133,7 @@ const update = async () => {
|
|||||||
if (status !== HARDWARE.display.status.value) {
|
if (status !== HARDWARE.display.status.value) {
|
||||||
console.log("Update Display Status:", getDisplayStatus());
|
console.log("Update Display Status:", getDisplayStatus());
|
||||||
HARDWARE.display.status.value = status;
|
HARDWARE.display.status.value = status;
|
||||||
HARDWARE.display.notifiers.forEach((notifier) => {
|
EVENTS.emit("updateDisplay");
|
||||||
notifier();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,23 +143,21 @@ const update = async () => {
|
|||||||
if (brightness !== HARDWARE.display.brightness.value) {
|
if (brightness !== HARDWARE.display.brightness.value) {
|
||||||
console.log("Update Display Brightness:", getDisplayBrightness());
|
console.log("Update Display Brightness:", getDisplayBrightness());
|
||||||
HARDWARE.display.brightness.value = brightness;
|
HARDWARE.display.brightness.value = brightness;
|
||||||
HARDWARE.display.notifiers.forEach((notifier) => {
|
EVENTS.emit("updateDisplay");
|
||||||
notifier();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies device compatibility by checking the presence of necessary sys paths.
|
* Verifies system compatibility by checking the presence of necessary sys paths.
|
||||||
*
|
*
|
||||||
* @returns {bool} Returns true if all paths exists.
|
* @returns {bool} Returns true if all paths exists.
|
||||||
*/
|
*/
|
||||||
const compatibleDevice = () => {
|
const compatibleSystem = () => {
|
||||||
if (os.platform() !== "linux") {
|
if (os.platform() !== "linux") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const paths = ["/sys/class/drm", "/sys/class/backlight", "/sys/class/thermal"];
|
const paths = ["/sys/class/drm", "/sys/class/backlight", "/sys/class/power_supply", "/sys/class/thermal"];
|
||||||
return paths.every((path) => fs.existsSync(path));
|
return paths.every((path) => fs.existsSync(path));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -179,12 +190,37 @@ const sessionType = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the session type for the logged in user.
|
* Gets the desktop environment name by checking environment variables.
|
||||||
*
|
*
|
||||||
* @returns {bool} Returns true if the session matches the type.
|
* @returns {string} Returns desktop environment name or 'unknown' if not detected.
|
||||||
*/
|
*/
|
||||||
const session = (type) => {
|
const sessionDesktop = () => {
|
||||||
return HARDWARE.session.type === type;
|
const envs = ["XDG_CURRENT_DESKTOP", "XDG_DESKTOP_SESSION", "DESKTOP_SESSION"];
|
||||||
|
const names = envs.map((env) => process.env[env]).filter(Boolean);
|
||||||
|
return (names.join(":") || "unknown").toLowerCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks supported features based on hardware and software.
|
||||||
|
*
|
||||||
|
* @returns {Object} Returns support object with boolean values.
|
||||||
|
*/
|
||||||
|
const checkSupport = () => {
|
||||||
|
const battery = HARDWARE.battery.level;
|
||||||
|
const status = HARDWARE.display.status;
|
||||||
|
const brightness = HARDWARE.display.brightness;
|
||||||
|
const keyboard = processRuns("squeekboard");
|
||||||
|
const service = serviceRuns(APP.name);
|
||||||
|
const sudo = sudoRights();
|
||||||
|
const deb = APP.build.maker === "deb";
|
||||||
|
return {
|
||||||
|
batteryLevel: !!battery.path,
|
||||||
|
displayStatus: !!status.path && !!status.command,
|
||||||
|
displayBrightness: sudo && !!brightness.path && !!brightness.max,
|
||||||
|
keyboardVisibility: keyboard,
|
||||||
|
sudoRights: sudo,
|
||||||
|
appUpdate: sudo && service && deb,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -196,7 +232,7 @@ const getModel = () => {
|
|||||||
const paths = ["/sys/firmware/devicetree/base/model", "/sys/class/dmi/id/product_name"];
|
const paths = ["/sys/firmware/devicetree/base/model", "/sys/class/dmi/id/product_name"];
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (fs.existsSync(path)) {
|
if (fs.existsSync(path)) {
|
||||||
return execSyncCommand("sudo", ["cat", path]) || "Generic";
|
return execSyncCommand("cat", [path]) || "Generic";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "Generic";
|
return "Generic";
|
||||||
@@ -211,7 +247,7 @@ const getVendor = () => {
|
|||||||
const paths = ["/sys/class/dmi/id/board_vendor"];
|
const paths = ["/sys/class/dmi/id/board_vendor"];
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (fs.existsSync(path)) {
|
if (fs.existsSync(path)) {
|
||||||
return execSyncCommand("sudo", ["cat", path]) || "Generic";
|
return execSyncCommand("cat", [path]) || "Generic";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const model = getModel();
|
const model = getModel();
|
||||||
@@ -222,22 +258,22 @@ const getVendor = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the serial number using `/sys/firmware/devicetree/base/serial-number` or `/sys/class/dmi/id/product_serial`.
|
* Gets the serial number using `/sys/firmware/devicetree/base/serial-number`.
|
||||||
*
|
*
|
||||||
* @returns {string} The serial number of the device or '123456' if not found.
|
* @returns {string} The serial number or machine id parts of the device or '123456' if not found.
|
||||||
*/
|
*/
|
||||||
const getSerialNumber = () => {
|
const getSerialNumber = () => {
|
||||||
const paths = ["/sys/firmware/devicetree/base/serial-number", "/sys/class/dmi/id/product_serial"];
|
const paths = ["/sys/firmware/devicetree/base/serial-number"];
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (fs.existsSync(path)) {
|
if (fs.existsSync(path)) {
|
||||||
return execSyncCommand("sudo", ["cat", path]) || "123456";
|
return execSyncCommand("cat", [path]) || "123456";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "123456";
|
return getMachineId().slice(-6);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the host machine id using `/etc/machine-id`.
|
* Gets the machine id using `/etc/machine-id`.
|
||||||
*
|
*
|
||||||
* @returns {string} The machine id of the system or '123456' if not found.
|
* @returns {string} The machine id of the system or '123456' if not found.
|
||||||
*/
|
*/
|
||||||
@@ -245,12 +281,38 @@ const getMachineId = () => {
|
|||||||
const paths = ["/etc/machine-id"];
|
const paths = ["/etc/machine-id"];
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (fs.existsSync(path)) {
|
if (fs.existsSync(path)) {
|
||||||
return execSyncCommand("sudo", ["cat", path]) || "123456";
|
return execSyncCommand("cat", [path]) || "123456";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "123456";
|
return "123456";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the network interfaces addresses using `os.networkInterfaces()`.
|
||||||
|
*
|
||||||
|
* @returns {Object} The network addresses of all interfaces.
|
||||||
|
*/
|
||||||
|
const getNetworkAddresses = () => {
|
||||||
|
const addresses = {};
|
||||||
|
for (const [key, interfaces] of Object.entries(os.networkInterfaces())) {
|
||||||
|
for (const interface of interfaces) {
|
||||||
|
if (interface.internal || !interface.family || !interface.address) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const name = key.charAt(0).toUpperCase() + key.slice(1);
|
||||||
|
if (!addresses[name]) {
|
||||||
|
addresses[name] = {};
|
||||||
|
}
|
||||||
|
const family = interface.family;
|
||||||
|
if (!addresses[name][family]) {
|
||||||
|
addresses[name][family] = [];
|
||||||
|
}
|
||||||
|
addresses[name][family].push(interface.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the host name of the current system using `os.hostname()`.
|
* Gets the host name of the current system using `os.hostname()`.
|
||||||
*
|
*
|
||||||
@@ -371,7 +433,31 @@ const getDisplayStatusPath = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current display power status using `wlopm` or `xset`.
|
* Gets the available display status command checking for `wlopm`, `kscreen-doctor` and `xset`.
|
||||||
|
*
|
||||||
|
* @returns {string|null} The display status command or null if nothing was found.
|
||||||
|
*/
|
||||||
|
const getDisplayStatusCommand = () => {
|
||||||
|
const type = HARDWARE.session.type;
|
||||||
|
const desktop = HARDWARE.session.desktop;
|
||||||
|
const mapping = {
|
||||||
|
wayland: [
|
||||||
|
{ command: "wlopm", desktops: ["labwc", "wayfire", "unknown"] },
|
||||||
|
{ command: "kscreen-doctor", desktops: ["kde", "plasma", "unknown"] },
|
||||||
|
],
|
||||||
|
x11: [{ command: "xset", desktops: ["*"] }],
|
||||||
|
}[type];
|
||||||
|
for (const map of mapping || []) {
|
||||||
|
if (commandExists(map.command) && map.desktops.some((d) => d === "*" || desktop.includes(d))) {
|
||||||
|
return map.command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.warn("No display command available");
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current display power status using the available command.
|
||||||
*
|
*
|
||||||
* @returns {string|null} The display status as 'ON'/'OFF' or null if an error occurs.
|
* @returns {string|null} The display status as 'ON'/'OFF' or null if an error occurs.
|
||||||
*/
|
*/
|
||||||
@@ -379,24 +465,34 @@ const getDisplayStatus = () => {
|
|||||||
if (!HARDWARE.support.displayStatus) {
|
if (!HARDWARE.support.displayStatus) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (session("wayland") && commandExists("wlopm")) {
|
switch (HARDWARE.display.status.command) {
|
||||||
const status = execSyncCommand("wlopm", []);
|
case "wlopm":
|
||||||
if (status !== null) {
|
const wlopm = execSyncCommand("wlopm", []);
|
||||||
const out = status.split("\n")[0].split(" ");
|
if (wlopm !== null) {
|
||||||
return out.pop().toUpperCase();
|
const output = wlopm.split("\n")[0].split(" ");
|
||||||
}
|
return output.pop().toUpperCase();
|
||||||
} else if (session("x11") && commandExists("xset")) {
|
}
|
||||||
const status = execSyncCommand("xset", ["-q"]);
|
break;
|
||||||
if (status !== null) {
|
case "kscreen-doctor":
|
||||||
const on = /Monitor is On/.test(status);
|
const kdoc = execSyncCommand("kscreen-doctor", ["--dpms", "show"]);
|
||||||
return on ? "ON" : "OFF";
|
if (kdoc !== null) {
|
||||||
}
|
const output = kdoc.split("\n")[0].split(" ");
|
||||||
|
return output.pop().toUpperCase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "xset":
|
||||||
|
const xset = execSyncCommand("xset", ["-q"]);
|
||||||
|
if (xset !== null) {
|
||||||
|
const output = /Monitor is On/.test(xset);
|
||||||
|
return output ? "ON" : "OFF";
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the display power status using `wlopm` or `xset`.
|
* Sets the display power status using the available command.
|
||||||
*
|
*
|
||||||
* This function takes a desired status ('ON' or 'OFF') and executes
|
* This function takes a desired status ('ON' or 'OFF') and executes
|
||||||
* the appropriate command to set the display status.
|
* the appropriate command to set the display status.
|
||||||
@@ -414,10 +510,16 @@ const setDisplayStatus = (status, callback = null) => {
|
|||||||
if (typeof callback === "function") callback(null, "Invalid status");
|
if (typeof callback === "function") callback(null, "Invalid status");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (session("wayland") && commandExists("wlopm")) {
|
switch (HARDWARE.display.status.command) {
|
||||||
execAsyncCommand("wlopm", [`--${status.toLowerCase()}`, "*"], callback);
|
case "wlopm":
|
||||||
} else if (session("x11") && commandExists("xset")) {
|
execAsyncCommand("wlopm", [`--${status.toLowerCase()}`, "*"], callback);
|
||||||
execAsyncCommand("xset", ["dpms", "force", status.toLowerCase()], callback);
|
break;
|
||||||
|
case "kscreen-doctor":
|
||||||
|
execAsyncCommand("kscreen-doctor", ["--dpms", status.toLowerCase()], callback);
|
||||||
|
break;
|
||||||
|
case "xset":
|
||||||
|
execAsyncCommand("xset", ["dpms", "force", status.toLowerCase()], callback);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -444,7 +546,7 @@ const getDisplayBrightnessPath = () => {
|
|||||||
* @returns {number|null} The brightness maximum value or null if an error occurs.
|
* @returns {number|null} The brightness maximum value or null if an error occurs.
|
||||||
*/
|
*/
|
||||||
const getDisplayBrightnessMax = () => {
|
const getDisplayBrightnessMax = () => {
|
||||||
if (!HARDWARE.support.displayBrightness) {
|
if (!HARDWARE.display.brightness.path) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const max = fs.readFileSync(`${HARDWARE.display.brightness.path}/max_brightness`, "utf8").trim();
|
const max = fs.readFileSync(`${HARDWARE.display.brightness.path}/max_brightness`, "utf8").trim();
|
||||||
@@ -490,7 +592,7 @@ const setDisplayBrightness = (brightness, callback = null) => {
|
|||||||
if (typeof callback === "function") callback(null, "Invalid brightness");
|
if (typeof callback === "function") callback(null, "Invalid brightness");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const max = HARDWARE.display.brightness.max;
|
const max = HARDWARE.display.brightness.max || 1;
|
||||||
const value = Math.max(1, Math.min(Math.round((brightness / 100) * max), max));
|
const value = Math.max(1, Math.min(Math.round((brightness / 100) * max), max));
|
||||||
const proc = execAsyncCommand("sudo", ["tee", `${HARDWARE.display.brightness.path}/brightness`], callback);
|
const proc = execAsyncCommand("sudo", ["tee", `${HARDWARE.display.brightness.path}/brightness`], callback);
|
||||||
proc.stdin.write(value.toString());
|
proc.stdin.write(value.toString());
|
||||||
@@ -557,6 +659,10 @@ const checkPackageUpgrades = () => {
|
|||||||
* @param {Function} callback - A callback function that receives the output or error.
|
* @param {Function} callback - A callback function that receives the output or error.
|
||||||
*/
|
*/
|
||||||
const shutdownSystem = (callback = null) => {
|
const shutdownSystem = (callback = null) => {
|
||||||
|
if (!HARDWARE.support.sudoRights) {
|
||||||
|
if (typeof callback === "function") callback(null, "Not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
execAsyncCommand("sudo", ["shutdown", "-h", "now"], callback);
|
execAsyncCommand("sudo", ["shutdown", "-h", "now"], callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -569,9 +675,39 @@ const shutdownSystem = (callback = null) => {
|
|||||||
* @param {Function} callback - A callback function that receives the output or error.
|
* @param {Function} callback - A callback function that receives the output or error.
|
||||||
*/
|
*/
|
||||||
const rebootSystem = (callback = null) => {
|
const rebootSystem = (callback = null) => {
|
||||||
|
if (!HARDWARE.support.sudoRights) {
|
||||||
|
if (typeof callback === "function") callback(null, "Not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
execAsyncCommand("sudo", ["reboot"], callback);
|
execAsyncCommand("sudo", ["reboot"], callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if sudo commands can run without a password.
|
||||||
|
*
|
||||||
|
* @returns {bool} Returns true if password-less sudo rights exists.
|
||||||
|
*/
|
||||||
|
const sudoRights = () => {
|
||||||
|
try {
|
||||||
|
const output = cpr.execSync(`sudo -n true`, { encoding: "utf8" });
|
||||||
|
return output !== null && !output.includes("password is required");
|
||||||
|
} catch {}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a service is running using `systemctl`.
|
||||||
|
*
|
||||||
|
* @param {string} name - The service name to check.
|
||||||
|
* @returns {bool} Returns true if the service runs.
|
||||||
|
*/
|
||||||
|
const serviceRuns = (name) => {
|
||||||
|
try {
|
||||||
|
return !!cpr.execSync(`systemctl --user is-active ${name}`, { encoding: "utf8" });
|
||||||
|
} catch {}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a process is running using `pidof`.
|
* Checks if a process is running using `pidof`.
|
||||||
*
|
*
|
||||||
@@ -648,14 +784,61 @@ const execAsyncCommand = (cmd, args = [], callback = null) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Execute Async:", error.message);
|
console.error("Execute Async:", error.message);
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") callback(null, error.message);
|
||||||
callback(null, error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return proc;
|
return proc;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a script command asynchronously.
|
||||||
|
*
|
||||||
|
* @param {string} cmd - The script to execute.
|
||||||
|
* @param {Array<string>} args - The arguments for the command.
|
||||||
|
* @param {Function} callback - A callback function that receives the progress or error.
|
||||||
|
* @returns {Object} The spawned process object.
|
||||||
|
*/
|
||||||
|
const execScriptCommand = (cmd, args = [], callback = null) => {
|
||||||
|
let progress = 1;
|
||||||
|
let proc = cpr.spawn(cmd, args);
|
||||||
|
if (typeof callback === "function") callback(progress, null);
|
||||||
|
proc.stdout.on("data", (data) => {
|
||||||
|
if (data) {
|
||||||
|
const output = data.toString().trim();
|
||||||
|
const lines = output.replace(/\n+/g, "\n").replace(/^\n+|\n+$|\0/g, "");
|
||||||
|
for (const line of lines.split("\n").filter(Boolean)) {
|
||||||
|
console.log(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
proc.stderr.on("data", (data) => {
|
||||||
|
if (data) {
|
||||||
|
const output = data.toString().trim();
|
||||||
|
const lines = output.replace(/\n+/g, "\n").replace(/^\n+|\n+$|\0/g, "");
|
||||||
|
for (const line of lines.split("\n").filter(Boolean)) {
|
||||||
|
const matches = line.match(/(\d{1,3})%/g);
|
||||||
|
if (matches) {
|
||||||
|
const match = Math.max(...matches.map((p) => parseInt(p)));
|
||||||
|
const percent = Math.floor(match / 10) * 10;
|
||||||
|
if (percent > 10 && percent > progress) {
|
||||||
|
progress = percent;
|
||||||
|
if (typeof callback === "function") callback(progress - 10, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
proc.on("close", (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
console.log(`Script exited with error code ${code}.`);
|
||||||
|
if (typeof callback === "function") callback(null, code);
|
||||||
|
} else {
|
||||||
|
console.log("Script exited successfully.");
|
||||||
|
if (typeof callback === "function") callback(100, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes a D-Bus method call synchronously using `dbus-send`.
|
* Executes a D-Bus method call synchronously using `dbus-send`.
|
||||||
*
|
*
|
||||||
@@ -670,15 +853,11 @@ const dbusCall = (path, method, values, callback = null) => {
|
|||||||
const dest = `${iface} ${path} ${iface}.${method} ${values.join(" ")}`;
|
const dest = `${iface} ${path} ${iface}.${method} ${values.join(" ")}`;
|
||||||
const args = ["--print-reply", "--type=method_call", `--dest=${dest}`];
|
const args = ["--print-reply", "--type=method_call", `--dest=${dest}`];
|
||||||
try {
|
try {
|
||||||
const output = cpr.execSync([cmd, ...args].join(" "), { encoding: "utf8" });
|
const output = cpr.execSync([cmd, ...args].join(" ").trim(), { encoding: "utf8" });
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") callback(output.trim().replace(/\0/g, ""), null);
|
||||||
callback(output.trim().replace(/\0/g, ""), null);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Call D-Bus:", error.message);
|
console.error("Call D-Bus:", error.message);
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") callback(null, error.message);
|
||||||
callback(null, error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -712,17 +891,13 @@ const dbusMonitor = (path, callback) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Monitor D-Bus:", error.message);
|
console.error("Monitor D-Bus:", error.message);
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") callback(null, error.message);
|
||||||
callback(null, error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
proc.stderr.on("data", (data) => {
|
proc.stderr.on("data", (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
console.error("Monitor D-Bus:", data.toString());
|
console.error("Monitor D-Bus:", data.toString());
|
||||||
if (typeof callback === "function") {
|
if (typeof callback === "function") callback(null, data.toString());
|
||||||
callback(null, data.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return proc;
|
return proc;
|
||||||
@@ -755,19 +930,16 @@ module.exports = {
|
|||||||
getVendor,
|
getVendor,
|
||||||
getSerialNumber,
|
getSerialNumber,
|
||||||
getMachineId,
|
getMachineId,
|
||||||
|
getNetworkAddresses,
|
||||||
getHostName,
|
getHostName,
|
||||||
getUpTime,
|
getUpTime,
|
||||||
getMemorySize,
|
getMemorySize,
|
||||||
getMemoryUsage,
|
getMemoryUsage,
|
||||||
getProcessorUsage,
|
getProcessorUsage,
|
||||||
getProcessorTemperature,
|
getProcessorTemperature,
|
||||||
getBatteryLevelPath,
|
|
||||||
getBatteryLevel,
|
getBatteryLevel,
|
||||||
getDisplayStatusPath,
|
|
||||||
getDisplayStatus,
|
getDisplayStatus,
|
||||||
setDisplayStatus,
|
setDisplayStatus,
|
||||||
getDisplayBrightnessPath,
|
|
||||||
getDisplayBrightnessMax,
|
|
||||||
getDisplayBrightness,
|
getDisplayBrightness,
|
||||||
setDisplayBrightness,
|
setDisplayBrightness,
|
||||||
getKeyboardVisibility,
|
getKeyboardVisibility,
|
||||||
@@ -775,4 +947,7 @@ module.exports = {
|
|||||||
checkPackageUpgrades,
|
checkPackageUpgrades,
|
||||||
shutdownSystem,
|
shutdownSystem,
|
||||||
rebootSystem,
|
rebootSystem,
|
||||||
|
execSyncCommand,
|
||||||
|
execAsyncCommand,
|
||||||
|
execScriptCommand,
|
||||||
};
|
};
|
||||||
|
|||||||
+242
-92
@@ -4,7 +4,6 @@ const { app } = require("electron");
|
|||||||
|
|
||||||
global.INTEGRATION = global.INTEGRATION || {
|
global.INTEGRATION = global.INTEGRATION || {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
status: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,14 +37,15 @@ const init = async () => {
|
|||||||
// Init globals
|
// Init globals
|
||||||
INTEGRATION.discovery = discovery;
|
INTEGRATION.discovery = discovery;
|
||||||
INTEGRATION.node = `rpi_${deviceId}`;
|
INTEGRATION.node = `rpi_${deviceId}`;
|
||||||
INTEGRATION.root = `${app.getName()}/${INTEGRATION.node}`;
|
INTEGRATION.root = `${APP.name}/${INTEGRATION.node}`;
|
||||||
INTEGRATION.device = {
|
INTEGRATION.device = {
|
||||||
name: `TouchKio ${deviceName}`,
|
name: `${APP.title} ${deviceName}`,
|
||||||
model: model,
|
model: model,
|
||||||
manufacturer: vendor,
|
manufacturer: vendor,
|
||||||
serial_number: serialNumber,
|
serial_number: serialNumber,
|
||||||
identifiers: [INTEGRATION.node],
|
identifiers: [INTEGRATION.node],
|
||||||
sw_version: `${app.getName()}-v${app.getVersion()}`,
|
sw_version: `${APP.name}-v${APP.version}`,
|
||||||
|
configuration_url: APP.homepage,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connection settings
|
// Connection settings
|
||||||
@@ -56,10 +56,12 @@ const init = async () => {
|
|||||||
|
|
||||||
// Client connected
|
// Client connected
|
||||||
INTEGRATION.client
|
INTEGRATION.client
|
||||||
.on("connect", () => {
|
.once("connect", () => {
|
||||||
console.log(`MQTT Connected: ${url.toString()}\n`);
|
console.log(`MQTT Connected: ${url.toString()}`);
|
||||||
|
process.stdout.write("\n");
|
||||||
|
|
||||||
// Init client controls
|
// Init client controls
|
||||||
|
initApp();
|
||||||
initShutdown();
|
initShutdown();
|
||||||
initReboot();
|
initReboot();
|
||||||
initRefresh();
|
initRefresh();
|
||||||
@@ -67,12 +69,14 @@ const init = async () => {
|
|||||||
initDisplay();
|
initDisplay();
|
||||||
initKeyboard();
|
initKeyboard();
|
||||||
initPageNumber();
|
initPageNumber();
|
||||||
|
initPageZoom();
|
||||||
initPageUrl();
|
initPageUrl();
|
||||||
|
|
||||||
// Init client sensors
|
// Init client sensors
|
||||||
initModel();
|
initModel();
|
||||||
initSerialNumber();
|
initSerialNumber();
|
||||||
initHostName();
|
initHostName();
|
||||||
|
initNetworkAddress();
|
||||||
initUpTime();
|
initUpTime();
|
||||||
initMemorySize();
|
initMemorySize();
|
||||||
initMemoryUsage();
|
initMemoryUsage();
|
||||||
@@ -85,20 +89,14 @@ const init = async () => {
|
|||||||
|
|
||||||
// Integration initialized
|
// Integration initialized
|
||||||
INTEGRATION.initialized = true;
|
INTEGRATION.initialized = true;
|
||||||
INTEGRATION.status = "Online";
|
|
||||||
})
|
})
|
||||||
.on("error", (error) => {
|
.on("error", (error) => {
|
||||||
console.error("MQTT:", error);
|
console.error("MQTT", error.message);
|
||||||
INTEGRATION.status = "Offline";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update sensor states from notifiers
|
// Update sensor states from events
|
||||||
HARDWARE.display.notifiers.push(() => {
|
EVENTS.on("updateDisplay", updateDisplay);
|
||||||
updateDisplay();
|
EVENTS.on("updateKeyboard", updateKeyboard);
|
||||||
});
|
|
||||||
HARDWARE.keyboard.notifiers.push(() => {
|
|
||||||
updateKeyboard();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update time sensors periodically (30s)
|
// Update time sensors periodically (30s)
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
@@ -111,8 +109,9 @@ const init = async () => {
|
|||||||
update();
|
update();
|
||||||
}, 60 * 1000);
|
}, 60 * 1000);
|
||||||
|
|
||||||
// Update package sensors periodically (60min)
|
// Update upgrade sensors periodically (1h)
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
|
updateApp();
|
||||||
updatePackageUpgrades();
|
updatePackageUpgrades();
|
||||||
}, 3600 * 1000);
|
}, 3600 * 1000);
|
||||||
|
|
||||||
@@ -130,7 +129,9 @@ const update = async () => {
|
|||||||
// Update client sensors
|
// Update client sensors
|
||||||
updateKiosk();
|
updateKiosk();
|
||||||
updatePageNumber();
|
updatePageNumber();
|
||||||
|
updatePageZoom();
|
||||||
updatePageUrl();
|
updatePageUrl();
|
||||||
|
updateNetworkAddress();
|
||||||
updateUpTime();
|
updateUpTime();
|
||||||
updateLastActive();
|
updateLastActive();
|
||||||
updateMemoryUsage();
|
updateMemoryUsage();
|
||||||
@@ -185,10 +186,68 @@ const publishState = (path, state) => {
|
|||||||
return INTEGRATION.client.publish(root, `${state}`, { qos: 1, retain: true });
|
return INTEGRATION.client.publish(root, `${state}`, { qos: 1, retain: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the app update entity and handles the execute logic.
|
||||||
|
*/
|
||||||
|
const initApp = () => {
|
||||||
|
if (!HARDWARE.support.appUpdate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const root = `${INTEGRATION.root}/app`;
|
||||||
|
const config = {
|
||||||
|
name: "App",
|
||||||
|
unique_id: `${INTEGRATION.node}_app`,
|
||||||
|
command_topic: `${root}/install`,
|
||||||
|
state_topic: `${root}/version/state`,
|
||||||
|
payload_install: "update",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
};
|
||||||
|
publishConfig("update", config)
|
||||||
|
.on("message", (topic, message) => {
|
||||||
|
if (topic === config.command_topic) {
|
||||||
|
console.log("Update App...");
|
||||||
|
hardware.setDisplayStatus("ON", () => {
|
||||||
|
const args = ["-c", `bash <(wget -qO- ${APP.scripts.install}) update`];
|
||||||
|
hardware.execScriptCommand("bash", args, (progress, error) => {
|
||||||
|
if (progress) {
|
||||||
|
console.log(`Progress: ${progress}%`);
|
||||||
|
}
|
||||||
|
updateApp(progress);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe(config.command_topic);
|
||||||
|
updateApp();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the app update entity via the mqtt connection.
|
||||||
|
*/
|
||||||
|
const updateApp = async (progress = 0) => {
|
||||||
|
const latest = APP.releases.latest;
|
||||||
|
if (!latest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const version = {
|
||||||
|
title: latest.title,
|
||||||
|
latest_version: latest.version,
|
||||||
|
installed_version: APP.version,
|
||||||
|
release_summary: latest.summary.slice(0, 250) + "...",
|
||||||
|
release_url: latest.url,
|
||||||
|
update_percentage: progress || null,
|
||||||
|
in_progress: progress && progress > 0 && progress < 100,
|
||||||
|
};
|
||||||
|
publishState("app/version", JSON.stringify(version));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the shutdown button and handles the execute logic.
|
* Initializes the shutdown button and handles the execute logic.
|
||||||
*/
|
*/
|
||||||
const initShutdown = () => {
|
const initShutdown = () => {
|
||||||
|
if (!HARDWARE.support.sudoRights) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const root = `${INTEGRATION.root}/shutdown`;
|
const root = `${INTEGRATION.root}/shutdown`;
|
||||||
const config = {
|
const config = {
|
||||||
name: "Shutdown",
|
name: "Shutdown",
|
||||||
@@ -201,8 +260,9 @@ const initShutdown = () => {
|
|||||||
.on("message", (topic, message) => {
|
.on("message", (topic, message) => {
|
||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
console.log("Shutdown system...");
|
console.log("Shutdown system...");
|
||||||
hardware.setDisplayStatus("ON");
|
hardware.setDisplayStatus("ON", () => {
|
||||||
hardware.shutdownSystem();
|
hardware.shutdownSystem();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic);
|
.subscribe(config.command_topic);
|
||||||
@@ -212,6 +272,9 @@ const initShutdown = () => {
|
|||||||
* Initializes the reboot button and handles the execute logic.
|
* Initializes the reboot button and handles the execute logic.
|
||||||
*/
|
*/
|
||||||
const initReboot = () => {
|
const initReboot = () => {
|
||||||
|
if (!HARDWARE.support.sudoRights) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const root = `${INTEGRATION.root}/reboot`;
|
const root = `${INTEGRATION.root}/reboot`;
|
||||||
const config = {
|
const config = {
|
||||||
name: "Reboot",
|
name: "Reboot",
|
||||||
@@ -224,8 +287,9 @@ const initReboot = () => {
|
|||||||
.on("message", (topic, message) => {
|
.on("message", (topic, message) => {
|
||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
console.log("Rebooting system...");
|
console.log("Rebooting system...");
|
||||||
hardware.setDisplayStatus("ON");
|
hardware.setDisplayStatus("ON", () => {
|
||||||
hardware.rebootSystem();
|
hardware.rebootSystem();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic);
|
.subscribe(config.command_topic);
|
||||||
@@ -247,8 +311,9 @@ const initRefresh = () => {
|
|||||||
.on("message", (topic, message) => {
|
.on("message", (topic, message) => {
|
||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
console.log("Refreshing webview...");
|
console.log("Refreshing webview...");
|
||||||
hardware.setDisplayStatus("ON");
|
hardware.setDisplayStatus("ON", () => {
|
||||||
WEBVIEW.views[WEBVIEW.viewActive].webContents.reloadIgnoringCache();
|
EVENTS.emit("reloadView");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic);
|
.subscribe(config.command_topic);
|
||||||
@@ -274,31 +339,32 @@ const initKiosk = () => {
|
|||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
const status = message.toString();
|
const status = message.toString();
|
||||||
console.log("Set Kiosk Status:", status);
|
console.log("Set Kiosk Status:", status);
|
||||||
hardware.setDisplayStatus("ON");
|
hardware.setDisplayStatus("ON", () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "Framed":
|
case "Framed":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
WEBVIEW.window.unmaximize();
|
WEBVIEW.window.unmaximize();
|
||||||
WEBVIEW.window.setFullScreen(false);
|
WEBVIEW.window.setFullScreen(false);
|
||||||
break;
|
break;
|
||||||
case "Fullscreen":
|
case "Fullscreen":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
WEBVIEW.window.unmaximize();
|
WEBVIEW.window.unmaximize();
|
||||||
WEBVIEW.window.setFullScreen(true);
|
WEBVIEW.window.setFullScreen(true);
|
||||||
break;
|
break;
|
||||||
case "Maximized":
|
case "Maximized":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
WEBVIEW.window.setFullScreen(false);
|
WEBVIEW.window.setFullScreen(false);
|
||||||
WEBVIEW.window.maximize();
|
WEBVIEW.window.maximize();
|
||||||
break;
|
break;
|
||||||
case "Minimized":
|
case "Minimized":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
WEBVIEW.window.setFullScreen(false);
|
WEBVIEW.window.setFullScreen(false);
|
||||||
WEBVIEW.window.minimize();
|
WEBVIEW.window.minimize();
|
||||||
break;
|
break;
|
||||||
case "Terminated":
|
case "Terminated":
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic);
|
.subscribe(config.command_topic);
|
||||||
@@ -308,8 +374,8 @@ const initKiosk = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the kiosk status via the mqtt connection.
|
* Updates the kiosk status via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateKiosk = () => {
|
const updateKiosk = async () => {
|
||||||
const kiosk = WEBVIEW.status;
|
const kiosk = WEBVIEW.tracker.status;
|
||||||
publishState("kiosk", kiosk);
|
publishState("kiosk", kiosk);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -340,13 +406,23 @@ const initDisplay = () => {
|
|||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
const status = message.toString();
|
const status = message.toString();
|
||||||
console.log("Set Display Status:", status);
|
console.log("Set Display Status:", status);
|
||||||
hardware.setDisplayStatus(status);
|
hardware.setDisplayStatus(status, (reply, error) => {
|
||||||
hardware.update();
|
if (!error) {
|
||||||
|
hardware.update();
|
||||||
|
} else {
|
||||||
|
console.warn("Failed:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
} else if (topic === config.brightness_command_topic) {
|
} else if (topic === config.brightness_command_topic) {
|
||||||
const brightness = parseInt(message, 10);
|
const brightness = parseInt(message, 10);
|
||||||
console.log("Set Display Brightness:", brightness);
|
console.log("Set Display Brightness:", brightness);
|
||||||
hardware.setDisplayBrightness(brightness);
|
hardware.setDisplayBrightness(brightness, (reply, error) => {
|
||||||
hardware.update();
|
if (!error) {
|
||||||
|
hardware.update();
|
||||||
|
} else {
|
||||||
|
console.warn("Failed:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic)
|
.subscribe(config.command_topic)
|
||||||
@@ -357,7 +433,7 @@ const initDisplay = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the display status, brightness via the mqtt connection.
|
* Updates the display status, brightness via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateDisplay = () => {
|
const updateDisplay = async () => {
|
||||||
const status = hardware.getDisplayStatus();
|
const status = hardware.getDisplayStatus();
|
||||||
const brightness = hardware.getDisplayBrightness();
|
const brightness = hardware.getDisplayBrightness();
|
||||||
publishState("display/power", status);
|
publishState("display/power", status);
|
||||||
@@ -385,19 +461,21 @@ const initKeyboard = () => {
|
|||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
const status = message.toString();
|
const status = message.toString();
|
||||||
console.log("Set Keyboard Visibility:", status);
|
console.log("Set Keyboard Visibility:", status);
|
||||||
hardware.setKeyboardVisibility(status);
|
hardware.setDisplayStatus("ON", () => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "OFF":
|
case "OFF":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
WEBVIEW.window.unmaximize();
|
WEBVIEW.window.unmaximize();
|
||||||
WEBVIEW.window.setFullScreen(true);
|
WEBVIEW.window.setFullScreen(true);
|
||||||
break;
|
break;
|
||||||
case "ON":
|
case "ON":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
WEBVIEW.window.setFullScreen(false);
|
WEBVIEW.window.setFullScreen(false);
|
||||||
WEBVIEW.window.maximize();
|
WEBVIEW.window.maximize();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
hardware.setKeyboardVisibility(status);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic);
|
.subscribe(config.command_topic);
|
||||||
@@ -407,7 +485,7 @@ const initKeyboard = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the keyboard visibility via the mqtt connection.
|
* Updates the keyboard visibility via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateKeyboard = () => {
|
const updateKeyboard = async () => {
|
||||||
const visibility = hardware.getKeyboardVisibility();
|
const visibility = hardware.getKeyboardVisibility();
|
||||||
publishState("keyboard", visibility);
|
publishState("keyboard", visibility);
|
||||||
};
|
};
|
||||||
@@ -416,7 +494,7 @@ const updateKeyboard = () => {
|
|||||||
* Initializes the page number and handles the execute logic.
|
* Initializes the page number and handles the execute logic.
|
||||||
*/
|
*/
|
||||||
const initPageNumber = () => {
|
const initPageNumber = () => {
|
||||||
if (WEBVIEW.viewUrls.length < 3) {
|
if (WEBVIEW.viewUrls.length <= 2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const root = `${INTEGRATION.root}/page_number`;
|
const root = `${INTEGRATION.root}/page_number`;
|
||||||
@@ -438,7 +516,8 @@ const initPageNumber = () => {
|
|||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
const number = parseInt(message, 10);
|
const number = parseInt(message, 10);
|
||||||
console.log("Set Page Number:", number);
|
console.log("Set Page Number:", number);
|
||||||
WEBVIEW.viewActive = number;
|
WEBVIEW.viewActive = number || 1;
|
||||||
|
EVENTS.emit("updateView");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic);
|
.subscribe(config.command_topic);
|
||||||
@@ -448,11 +527,50 @@ const initPageNumber = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the page number via the mqtt connection.
|
* Updates the page number via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updatePageNumber = () => {
|
const updatePageNumber = async () => {
|
||||||
const pageNumber = WEBVIEW.viewActive;
|
const pageNumber = WEBVIEW.viewActive || 1;
|
||||||
publishState("page_number", pageNumber);
|
publishState("page_number", pageNumber);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the page zoom and handles the execute logic.
|
||||||
|
*/
|
||||||
|
const initPageZoom = () => {
|
||||||
|
const root = `${INTEGRATION.root}/page_zoom`;
|
||||||
|
const config = {
|
||||||
|
name: "Page Zoom",
|
||||||
|
unique_id: `${INTEGRATION.node}_page_zoom`,
|
||||||
|
command_topic: `${root}/set`,
|
||||||
|
state_topic: `${root}/state`,
|
||||||
|
value_template: "{{ value | int }}",
|
||||||
|
mode: "slider",
|
||||||
|
min: 25,
|
||||||
|
max: 400,
|
||||||
|
unit_of_measurement: "%",
|
||||||
|
icon: "mdi:magnify-plus",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
};
|
||||||
|
publishConfig("number", config)
|
||||||
|
.on("message", (topic, message) => {
|
||||||
|
if (topic === config.command_topic) {
|
||||||
|
const zoom = parseInt(message, 10);
|
||||||
|
console.log("Set Page Zoom:", zoom);
|
||||||
|
WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.setZoomFactor(zoom / 100.0);
|
||||||
|
EVENTS.emit("updateView");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.subscribe(config.command_topic);
|
||||||
|
updatePageZoom();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the page zoom via the mqtt connection.
|
||||||
|
*/
|
||||||
|
const updatePageZoom = async () => {
|
||||||
|
const pageZoom = Math.round(WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.getZoomFactor() * 100.0);
|
||||||
|
publishState("page_zoom", pageZoom);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the page url and handles the execute logic.
|
* Initializes the page url and handles the execute logic.
|
||||||
*/
|
*/
|
||||||
@@ -473,7 +591,7 @@ const initPageUrl = () => {
|
|||||||
if (topic === config.command_topic) {
|
if (topic === config.command_topic) {
|
||||||
const url = message.toString();
|
const url = message.toString();
|
||||||
console.log("Set Page Url:", url);
|
console.log("Set Page Url:", url);
|
||||||
WEBVIEW.views[WEBVIEW.viewActive].webContents.loadURL(url);
|
WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.loadURL(url);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.subscribe(config.command_topic);
|
.subscribe(config.command_topic);
|
||||||
@@ -483,9 +601,9 @@ const initPageUrl = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the page url via the mqtt connection.
|
* Updates the page url via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updatePageUrl = () => {
|
const updatePageUrl = async () => {
|
||||||
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive || 1];
|
||||||
const currentUrl = WEBVIEW.views[WEBVIEW.viewActive].webContents.getURL();
|
const currentUrl = WEBVIEW.views[WEBVIEW.viewActive || 1].webContents.getURL();
|
||||||
const pageUrl = !currentUrl || currentUrl.startsWith("data:") ? defaultUrl : currentUrl;
|
const pageUrl = !currentUrl || currentUrl.startsWith("data:") ? defaultUrl : currentUrl;
|
||||||
publishState("page_url", pageUrl);
|
publishState("page_url", pageUrl);
|
||||||
};
|
};
|
||||||
@@ -499,6 +617,7 @@ const initModel = () => {
|
|||||||
name: "Model",
|
name: "Model",
|
||||||
unique_id: `${INTEGRATION.node}_model`,
|
unique_id: `${INTEGRATION.node}_model`,
|
||||||
state_topic: `${root}/state`,
|
state_topic: `${root}/state`,
|
||||||
|
json_attributes_topic: `${root}/attributes`,
|
||||||
value_template: "{{ value }}",
|
value_template: "{{ value }}",
|
||||||
icon: "mdi:raspberry-pi",
|
icon: "mdi:raspberry-pi",
|
||||||
device: INTEGRATION.device,
|
device: INTEGRATION.device,
|
||||||
@@ -510,9 +629,10 @@ const initModel = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the model sensor via the mqtt connection.
|
* Updates the model sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateModel = () => {
|
const updateModel = async () => {
|
||||||
const model = hardware.getModel();
|
const model = hardware.getModel();
|
||||||
publishState("model", model);
|
publishState("model", model);
|
||||||
|
publishAttributes("model", HARDWARE.support);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -535,11 +655,41 @@ const initSerialNumber = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the serial number sensor via the mqtt connection.
|
* Updates the serial number sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateSerialNumber = () => {
|
const updateSerialNumber = async () => {
|
||||||
const serialNumber = hardware.getSerialNumber();
|
const serialNumber = hardware.getSerialNumber();
|
||||||
publishState("serial_number", serialNumber);
|
publishState("serial_number", serialNumber);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the network address sensor.
|
||||||
|
*/
|
||||||
|
const initNetworkAddress = () => {
|
||||||
|
const root = `${INTEGRATION.root}/network_address`;
|
||||||
|
const config = {
|
||||||
|
name: "Network Address",
|
||||||
|
unique_id: `${INTEGRATION.node}_network_address`,
|
||||||
|
state_topic: `${root}/state`,
|
||||||
|
json_attributes_topic: `${root}/attributes`,
|
||||||
|
value_template: "{{ value }}",
|
||||||
|
icon: "mdi:ip-network",
|
||||||
|
device: INTEGRATION.device,
|
||||||
|
};
|
||||||
|
publishConfig("sensor", config);
|
||||||
|
updateNetworkAddress();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the network address sensor via the mqtt connection.
|
||||||
|
*/
|
||||||
|
const updateNetworkAddress = async () => {
|
||||||
|
const networkAddresses = hardware.getNetworkAddresses();
|
||||||
|
const [name] = Object.keys(networkAddresses);
|
||||||
|
const [family] = name ? Object.keys(networkAddresses[name]) : [];
|
||||||
|
const networkAddress = networkAddresses[name]?.[family]?.[0] || null;
|
||||||
|
publishState("network_address", networkAddress);
|
||||||
|
publishAttributes("network_address", networkAddresses);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the host name sensor.
|
* Initializes the host name sensor.
|
||||||
*/
|
*/
|
||||||
@@ -560,7 +710,7 @@ const initHostName = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the host name sensor via the mqtt connection.
|
* Updates the host name sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateHostName = () => {
|
const updateHostName = async () => {
|
||||||
const hostName = hardware.getHostName();
|
const hostName = hardware.getHostName();
|
||||||
publishState("host_name", hostName);
|
publishState("host_name", hostName);
|
||||||
};
|
};
|
||||||
@@ -586,7 +736,7 @@ const initUpTime = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the up time sensor via the mqtt connection.
|
* Updates the up time sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateUpTime = () => {
|
const updateUpTime = async () => {
|
||||||
const upTime = hardware.getUpTime();
|
const upTime = hardware.getUpTime();
|
||||||
publishState("up_time", upTime);
|
publishState("up_time", upTime);
|
||||||
};
|
};
|
||||||
@@ -612,7 +762,7 @@ const initMemorySize = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the memory size sensor via the mqtt connection.
|
* Updates the memory size sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateMemorySize = () => {
|
const updateMemorySize = async () => {
|
||||||
const memorySize = hardware.getMemorySize();
|
const memorySize = hardware.getMemorySize();
|
||||||
publishState("memory_size", memorySize);
|
publishState("memory_size", memorySize);
|
||||||
};
|
};
|
||||||
@@ -638,7 +788,7 @@ const initMemoryUsage = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the memory usage sensor via the mqtt connection.
|
* Updates the memory usage sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateMemoryUsage = () => {
|
const updateMemoryUsage = async () => {
|
||||||
const memoryUsage = hardware.getMemoryUsage();
|
const memoryUsage = hardware.getMemoryUsage();
|
||||||
publishState("memory_usage", memoryUsage);
|
publishState("memory_usage", memoryUsage);
|
||||||
};
|
};
|
||||||
@@ -664,7 +814,7 @@ const initProcessorUsage = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the processor usage sensor via the mqtt connection.
|
* Updates the processor usage sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateProcessorUsage = () => {
|
const updateProcessorUsage = async () => {
|
||||||
const processorUsage = hardware.getProcessorUsage();
|
const processorUsage = hardware.getProcessorUsage();
|
||||||
publishState("processor_usage", processorUsage);
|
publishState("processor_usage", processorUsage);
|
||||||
};
|
};
|
||||||
@@ -690,7 +840,7 @@ const initProcessorTemperature = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the processor temperature sensor via the mqtt connection.
|
* Updates the processor temperature sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateProcessorTemperature = () => {
|
const updateProcessorTemperature = async () => {
|
||||||
const processorTemperature = hardware.getProcessorTemperature();
|
const processorTemperature = hardware.getProcessorTemperature();
|
||||||
publishState("processor_temperature", processorTemperature);
|
publishState("processor_temperature", processorTemperature);
|
||||||
};
|
};
|
||||||
@@ -719,7 +869,7 @@ const initBatteryLevel = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the battery level sensor via the mqtt connection.
|
* Updates the battery level sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateBatteryLevel = () => {
|
const updateBatteryLevel = async () => {
|
||||||
const batteryLevel = hardware.getBatteryLevel();
|
const batteryLevel = hardware.getBatteryLevel();
|
||||||
publishState("battery_level", batteryLevel);
|
publishState("battery_level", batteryLevel);
|
||||||
};
|
};
|
||||||
@@ -745,14 +895,14 @@ const initPackageUpgrades = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the package upgrades sensor via the mqtt connection.
|
* Updates the package upgrades sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updatePackageUpgrades = () => {
|
const updatePackageUpgrades = async () => {
|
||||||
const packages = hardware.checkPackageUpgrades();
|
const packages = hardware.checkPackageUpgrades();
|
||||||
const attributes = {
|
const attributes = {
|
||||||
total: packages.length,
|
total: packages.length,
|
||||||
packages: packages.map((pkg) => pkg.replace(/\s*\[.*?\]\s*/g, "").trim()),
|
packages: packages.map((pkg) => pkg.replace(/\s*\[.*?\]\s*/g, "").trim()),
|
||||||
};
|
};
|
||||||
publishAttributes("package_upgrades", attributes);
|
|
||||||
publishState("package_upgrades", attributes.total);
|
publishState("package_upgrades", attributes.total);
|
||||||
|
publishAttributes("package_upgrades", attributes);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -775,7 +925,7 @@ const initHeartbeat = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the heartbeat sensor via the mqtt connection.
|
* Updates the heartbeat sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateHeartbeat = () => {
|
const updateHeartbeat = async () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const local = new Date(now.getTime() - now.getTimezoneOffset() * 60 * 1000);
|
const local = new Date(now.getTime() - now.getTimezoneOffset() * 60 * 1000);
|
||||||
const heartbeat = local.toISOString().replace(/\.\d{3}Z$/, "");
|
const heartbeat = local.toISOString().replace(/\.\d{3}Z$/, "");
|
||||||
@@ -803,10 +953,10 @@ const initLastActive = () => {
|
|||||||
/**
|
/**
|
||||||
* Updates the last active sensor via the mqtt connection.
|
* Updates the last active sensor via the mqtt connection.
|
||||||
*/
|
*/
|
||||||
const updateLastActive = () => {
|
const updateLastActive = async () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const then = WEBVIEW.pointer.time;
|
const then = WEBVIEW.tracker.pointer.time;
|
||||||
const lastActive = Math.abs(now - then) / (1000 * 60);
|
const lastActive = (now - then) / (1000 * 60);
|
||||||
publishState("last_active", lastActive);
|
publishState("last_active", lastActive);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+349
-104
@@ -1,15 +1,18 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const axios = require("axios");
|
||||||
const hardware = require("./hardware");
|
const hardware = require("./hardware");
|
||||||
const integration = require("./integration");
|
const integration = require("./integration");
|
||||||
const { app, screen, nativeTheme, ipcMain, session, BaseWindow, WebContentsView } = require("electron");
|
const { app, nativeTheme, globalShortcut, ipcMain, session, BaseWindow, WebContentsView } = require("electron");
|
||||||
|
|
||||||
global.WEBVIEW = global.WEBVIEW || {
|
global.WEBVIEW = global.WEBVIEW || {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
status: null,
|
tracker: {
|
||||||
locked: false,
|
pointer: {
|
||||||
pointer: {
|
position: {},
|
||||||
position: {},
|
time: new Date(),
|
||||||
time: new Date(),
|
},
|
||||||
|
display: {},
|
||||||
|
status: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,28 +40,21 @@ const init = async () => {
|
|||||||
const urls = [loaderHtml(40, 1.0, theme)].concat(ARGS.web_url);
|
const urls = [loaderHtml(40, 1.0, theme)].concat(ARGS.web_url);
|
||||||
|
|
||||||
// Init global properties
|
// Init global properties
|
||||||
|
WEBVIEW.viewActive = 0;
|
||||||
WEBVIEW.viewUrls = urls;
|
WEBVIEW.viewUrls = urls;
|
||||||
WEBVIEW.viewZoom = zoom;
|
WEBVIEW.viewZoom = zoom;
|
||||||
WEBVIEW.viewTheme = theme;
|
WEBVIEW.viewTheme = theme;
|
||||||
|
WEBVIEW.pagerEnabled = widget;
|
||||||
WEBVIEW.widgetTheme = theme;
|
WEBVIEW.widgetTheme = theme;
|
||||||
WEBVIEW.widgetEnabled = widget;
|
WEBVIEW.widgetEnabled = widget;
|
||||||
WEBVIEW.navigationTheme = theme;
|
WEBVIEW.navigationTheme = theme;
|
||||||
WEBVIEW.navigationEnabled = widget;
|
WEBVIEW.navigationEnabled = widget;
|
||||||
nativeTheme.themeSource = WEBVIEW.viewTheme;
|
nativeTheme.themeSource = WEBVIEW.viewTheme;
|
||||||
Object.defineProperty(WEBVIEW, "viewActive", {
|
|
||||||
get() {
|
|
||||||
return this._viewActive || 0;
|
|
||||||
},
|
|
||||||
set(v) {
|
|
||||||
this._viewActive = Math.max(0, Math.min(v, WEBVIEW.viewUrls.length - 1));
|
|
||||||
updateView();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Init global root window
|
// Init global root window
|
||||||
WEBVIEW.window = new BaseWindow({
|
WEBVIEW.window = new BaseWindow({
|
||||||
title: "TouchKio",
|
title: APP.title,
|
||||||
icon: path.join(__dirname, "..", "img", "icon.png"),
|
icon: path.join(APP.path, "img", "icon.png"),
|
||||||
});
|
});
|
||||||
WEBVIEW.window.setMenuBarVisibility(false);
|
WEBVIEW.window.setMenuBarVisibility(false);
|
||||||
WEBVIEW.window.setFullScreen(fullscreen);
|
WEBVIEW.window.setFullScreen(fullscreen);
|
||||||
@@ -78,10 +74,24 @@ const init = async () => {
|
|||||||
view.setVisible(i === 0);
|
view.setVisible(i === 0);
|
||||||
view.setBackgroundColor("#FFFFFFFF");
|
view.setBackgroundColor("#FFFFFFFF");
|
||||||
WEBVIEW.window.contentView.addChildView(view);
|
WEBVIEW.window.contentView.addChildView(view);
|
||||||
view.webContents.loadURL(url);
|
|
||||||
WEBVIEW.views.push(view);
|
WEBVIEW.views.push(view);
|
||||||
|
onlineStatus(url).then(() => {
|
||||||
|
view.webContents.loadURL(url);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Init global pager
|
||||||
|
WEBVIEW.pager = new WebContentsView({
|
||||||
|
transparent: true,
|
||||||
|
webPreferences: {
|
||||||
|
nodeIntegration: true,
|
||||||
|
contextIsolation: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
WEBVIEW.pager.setBackgroundColor("#00000000");
|
||||||
|
WEBVIEW.window.contentView.addChildView(WEBVIEW.pager);
|
||||||
|
WEBVIEW.pager.webContents.loadFile(path.join(APP.path, "html", "pager.html"));
|
||||||
|
|
||||||
// Init global widget
|
// Init global widget
|
||||||
WEBVIEW.widget = new WebContentsView({
|
WEBVIEW.widget = new WebContentsView({
|
||||||
transparent: true,
|
transparent: true,
|
||||||
@@ -92,7 +102,7 @@ const init = async () => {
|
|||||||
});
|
});
|
||||||
WEBVIEW.widget.setBackgroundColor("#00000000");
|
WEBVIEW.widget.setBackgroundColor("#00000000");
|
||||||
WEBVIEW.window.contentView.addChildView(WEBVIEW.widget);
|
WEBVIEW.window.contentView.addChildView(WEBVIEW.widget);
|
||||||
WEBVIEW.widget.webContents.loadFile(path.join(app.getAppPath(), "html", "widget.html"));
|
WEBVIEW.widget.webContents.loadFile(path.join(APP.path, "html", "widget.html"));
|
||||||
|
|
||||||
// Init global navigation
|
// Init global navigation
|
||||||
WEBVIEW.navigation = new WebContentsView({
|
WEBVIEW.navigation = new WebContentsView({
|
||||||
@@ -104,14 +114,24 @@ const init = async () => {
|
|||||||
});
|
});
|
||||||
WEBVIEW.navigation.setBackgroundColor("#00000000");
|
WEBVIEW.navigation.setBackgroundColor("#00000000");
|
||||||
WEBVIEW.window.contentView.addChildView(WEBVIEW.navigation);
|
WEBVIEW.window.contentView.addChildView(WEBVIEW.navigation);
|
||||||
WEBVIEW.navigation.webContents.loadFile(path.join(app.getAppPath(), "html", "navigation.html"));
|
WEBVIEW.navigation.webContents.loadFile(path.join(APP.path, "html", "navigation.html"));
|
||||||
|
|
||||||
// Register events
|
// Register global events
|
||||||
windowEvents();
|
EVENTS.on("reloadView", reloadView);
|
||||||
widgetEvents();
|
EVENTS.on("updateView", updateView);
|
||||||
navigationEvents();
|
EVENTS.on("updateDisplay", () => {
|
||||||
domEvents();
|
const status = hardware.getDisplayStatus();
|
||||||
appEvents();
|
if (status) {
|
||||||
|
WEBVIEW.tracker.display[status.toLowerCase()] = new Date();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register local events
|
||||||
|
await windowEvents();
|
||||||
|
await widgetEvents();
|
||||||
|
await navigationEvents();
|
||||||
|
await viewEvents();
|
||||||
|
await appEvents();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -124,15 +144,15 @@ const update = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update windows status
|
// Update window status
|
||||||
if (WEBVIEW.window.isFullScreen()) {
|
if (WEBVIEW.window.isFullScreen()) {
|
||||||
WEBVIEW.status = "Fullscreen";
|
WEBVIEW.tracker.status = "Fullscreen";
|
||||||
} else if (WEBVIEW.window.isMinimized()) {
|
} else if (WEBVIEW.window.isMinimized()) {
|
||||||
WEBVIEW.status = "Minimized";
|
WEBVIEW.tracker.status = "Minimized";
|
||||||
} else if (WEBVIEW.window.isMaximized()) {
|
} else if (WEBVIEW.window.isMaximized()) {
|
||||||
WEBVIEW.status = "Maximized";
|
WEBVIEW.tracker.status = "Maximized";
|
||||||
} else {
|
} else {
|
||||||
WEBVIEW.status = "Framed";
|
WEBVIEW.tracker.status = "Framed";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update widget status
|
// Update widget status
|
||||||
@@ -142,7 +162,7 @@ const update = async () => {
|
|||||||
updateNavigation();
|
updateNavigation();
|
||||||
|
|
||||||
// Update integration sensor
|
// Update integration sensor
|
||||||
console.log("Update Kiosk Status:", WEBVIEW.status);
|
console.log("Update Kiosk Status:", WEBVIEW.tracker.status);
|
||||||
integration.update();
|
integration.update();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,8 +170,15 @@ const update = async () => {
|
|||||||
* Updates the active view.
|
* Updates the active view.
|
||||||
*/
|
*/
|
||||||
const updateView = () => {
|
const updateView = () => {
|
||||||
const title = `TouchKio - ${new URL(WEBVIEW.viewUrls[WEBVIEW.viewActive]).host}`;
|
if (!WEBVIEW.viewActive) {
|
||||||
console.log(`Update View: ${title} (${WEBVIEW.viewActive})`);
|
return;
|
||||||
|
}
|
||||||
|
const url = WEBVIEW.views[WEBVIEW.viewActive].webContents.getURL();
|
||||||
|
const host = url.startsWith("data:") ? "whoopsie" : new URL(url).host;
|
||||||
|
const title = `${APP.title} - ${host} (${WEBVIEW.viewActive})`;
|
||||||
|
|
||||||
|
// Update window title
|
||||||
|
console.log(`Update View: ${title}`);
|
||||||
WEBVIEW.window.setTitle(title);
|
WEBVIEW.window.setTitle(title);
|
||||||
|
|
||||||
// Hide all other webviews and show only the active one
|
// Hide all other webviews and show only the active one
|
||||||
@@ -186,17 +213,41 @@ const updateNavigation = () => {
|
|||||||
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
||||||
const currentUrl = view.webContents.getURL();
|
const currentUrl = view.webContents.getURL();
|
||||||
|
|
||||||
// Hide pager button
|
// Hide pager buttons
|
||||||
WEBVIEW.navigation.webContents.send("button-hidden", {
|
WEBVIEW.navigation.webContents.send("button-hidden", {
|
||||||
id: "pager",
|
id: "previous",
|
||||||
hidden: WEBVIEW.viewUrls.length < 3,
|
hidden: WEBVIEW.viewUrls.length <= 2,
|
||||||
|
});
|
||||||
|
WEBVIEW.navigation.webContents.send("button-hidden", {
|
||||||
|
id: "next",
|
||||||
|
hidden: WEBVIEW.viewUrls.length <= 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable pager buttons
|
||||||
|
WEBVIEW.navigation.webContents.send("button-disabled", {
|
||||||
|
id: "previous",
|
||||||
|
disabled: WEBVIEW.viewActive <= 1,
|
||||||
|
});
|
||||||
|
WEBVIEW.navigation.webContents.send("button-disabled", {
|
||||||
|
id: "next",
|
||||||
|
disabled: WEBVIEW.viewActive >= WEBVIEW.viewUrls.length - 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update url text
|
// Update url text
|
||||||
WEBVIEW.navigation.webContents.send("input-text", {
|
WEBVIEW.navigation.webContents.send("input-text", {
|
||||||
id: "url",
|
id: "url",
|
||||||
text: currentUrl.startsWith("data:") ? "" : currentUrl,
|
text: currentUrl.startsWith("data:") ? "" : currentUrl,
|
||||||
placeholder: defaultUrl,
|
placeholder: defaultUrl.startsWith("data:") ? "" : defaultUrl,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable zoom buttons
|
||||||
|
WEBVIEW.navigation.webContents.send("button-disabled", {
|
||||||
|
id: "minus",
|
||||||
|
disabled: view.webContents.getZoomFactor().toFixed(2) <= 0.25,
|
||||||
|
});
|
||||||
|
WEBVIEW.navigation.webContents.send("button-disabled", {
|
||||||
|
id: "plus",
|
||||||
|
disabled: view.webContents.getZoomFactor().toFixed(2) >= 4.0,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Disable history buttons
|
// Disable history buttons
|
||||||
@@ -225,11 +276,34 @@ const toggleNavigation = () => {
|
|||||||
width: window.width,
|
width: window.width,
|
||||||
height: height,
|
height: height,
|
||||||
});
|
});
|
||||||
|
if (height > 0) {
|
||||||
|
WEBVIEW.navigation.webContents.focus();
|
||||||
|
} else {
|
||||||
|
WEBVIEW.views[WEBVIEW.viewActive].webContents.focus();
|
||||||
|
}
|
||||||
|
|
||||||
// Resize webview
|
// Resize webview
|
||||||
resizeView();
|
resizeView();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decreases page zoom on the active webview.
|
||||||
|
*/
|
||||||
|
const zoomMinus = () => {
|
||||||
|
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||||
|
view.webContents.setZoomFactor(Math.max(0.25, view.webContents.getZoomFactor() - 0.1));
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increases page zoom on the active webview.
|
||||||
|
*/
|
||||||
|
const zoomPlus = () => {
|
||||||
|
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||||
|
view.webContents.setZoomFactor(Math.min(4.0, view.webContents.getZoomFactor() + 0.1));
|
||||||
|
update();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates backward in the history of the active webview.
|
* Navigates backward in the history of the active webview.
|
||||||
*/
|
*/
|
||||||
@@ -251,16 +325,25 @@ const historyForward = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activates the next webview or cycles back to the first.
|
* Activates the previous webview page.
|
||||||
*/
|
*/
|
||||||
const nextView = () => {
|
const previousView = () => {
|
||||||
WEBVIEW.viewActive = (WEBVIEW.viewActive + 1) % WEBVIEW.views.length || 1;
|
if (WEBVIEW.viewActive > 1) WEBVIEW.viewActive--;
|
||||||
|
updateView();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reloads the current or default url on the active webview.
|
* Activates the next webview page.
|
||||||
*/
|
*/
|
||||||
const reloadView = () => {
|
const nextView = () => {
|
||||||
|
if (WEBVIEW.viewActive < WEBVIEW.views.length - 1) WEBVIEW.viewActive++;
|
||||||
|
updateView();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the default url and settings on the active webview.
|
||||||
|
*/
|
||||||
|
const homeView = () => {
|
||||||
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||||
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
||||||
const currentUrl = view.webContents.getURL();
|
const currentUrl = view.webContents.getURL();
|
||||||
@@ -271,6 +354,30 @@ const reloadView = () => {
|
|||||||
} else {
|
} else {
|
||||||
view.webContents.reloadIgnoringCache();
|
view.webContents.reloadIgnoringCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset page zoom and history
|
||||||
|
view.webContents.setZoomFactor(WEBVIEW.viewZoom);
|
||||||
|
setTimeout(() => {
|
||||||
|
view.webContents.navigationHistory.clear();
|
||||||
|
update();
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the current url on the active webview.
|
||||||
|
*/
|
||||||
|
const reloadView = () => {
|
||||||
|
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||||
|
const defaultUrl = WEBVIEW.viewUrls[WEBVIEW.viewActive];
|
||||||
|
const currentUrl = view.webContents.getURL();
|
||||||
|
|
||||||
|
// Reload the default url or refresh the page
|
||||||
|
if (currentUrl.startsWith("data:")) {
|
||||||
|
view.webContents.loadURL(defaultUrl);
|
||||||
|
} else {
|
||||||
|
view.webContents.reloadIgnoringCache();
|
||||||
|
}
|
||||||
|
update();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -279,6 +386,7 @@ const reloadView = () => {
|
|||||||
const resizeView = () => {
|
const resizeView = () => {
|
||||||
const window = WEBVIEW.window.getBounds();
|
const window = WEBVIEW.window.getBounds();
|
||||||
const navigation = WEBVIEW.navigation.getBounds();
|
const navigation = WEBVIEW.navigation.getBounds();
|
||||||
|
const pager = { width: 20, height: window.height };
|
||||||
const widget = { width: 60, height: 200 };
|
const widget = { width: 60, height: 200 };
|
||||||
|
|
||||||
// Update view size
|
// Update view size
|
||||||
@@ -289,13 +397,23 @@ const resizeView = () => {
|
|||||||
width: window.width,
|
width: window.width,
|
||||||
height: window.height - navigation.height,
|
height: window.height - navigation.height,
|
||||||
});
|
});
|
||||||
view.webContents.setZoomFactor(WEBVIEW.viewZoom);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update pager size
|
||||||
|
if (WEBVIEW.pagerEnabled) {
|
||||||
|
WEBVIEW.pager.setBounds({
|
||||||
|
x: window.width - pager.width,
|
||||||
|
y: 0,
|
||||||
|
width: pager.width,
|
||||||
|
height: pager.height,
|
||||||
|
});
|
||||||
|
WEBVIEW.pager.webContents.send("data-theme", { theme: "hidden" });
|
||||||
|
}
|
||||||
|
|
||||||
// Update widget size
|
// Update widget size
|
||||||
if (WEBVIEW.widgetEnabled) {
|
if (WEBVIEW.widgetEnabled) {
|
||||||
WEBVIEW.widget.setBounds({
|
WEBVIEW.widget.setBounds({
|
||||||
x: window.width - 15,
|
x: window.width - 20,
|
||||||
y: parseInt(window.height / 2 - widget.height / 2, 10),
|
y: parseInt(window.height / 2 - widget.height / 2, 10),
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
@@ -318,27 +436,12 @@ const resizeView = () => {
|
|||||||
/**
|
/**
|
||||||
* Register window events and handler.
|
* Register window events and handler.
|
||||||
*/
|
*/
|
||||||
const windowEvents = () => {
|
const windowEvents = async () => {
|
||||||
// Handle window resize events
|
// Handle window resize events
|
||||||
WEBVIEW.window.on("ready-to-show", resizeView);
|
WEBVIEW.window.on("ready-to-show", resizeView);
|
||||||
WEBVIEW.window.on("resize", resizeView);
|
WEBVIEW.window.on("resize", resizeView);
|
||||||
resizeView();
|
resizeView();
|
||||||
|
|
||||||
// Handle window focus events
|
|
||||||
WEBVIEW.window.on("focus", () => {
|
|
||||||
if (WEBVIEW.locked) {
|
|
||||||
hardware.setDisplayStatus("ON");
|
|
||||||
WEBVIEW.window.blur();
|
|
||||||
}
|
|
||||||
WEBVIEW.locked = false;
|
|
||||||
});
|
|
||||||
HARDWARE.display.notifiers.push(() => {
|
|
||||||
WEBVIEW.locked = hardware.getDisplayStatus() === "OFF";
|
|
||||||
if (WEBVIEW.locked) {
|
|
||||||
WEBVIEW.window.blur();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle window status updates
|
// Handle window status updates
|
||||||
WEBVIEW.window.on("minimize", update);
|
WEBVIEW.window.on("minimize", update);
|
||||||
WEBVIEW.window.on("restore", update);
|
WEBVIEW.window.on("restore", update);
|
||||||
@@ -347,39 +450,46 @@ const windowEvents = () => {
|
|||||||
WEBVIEW.window.on("enter-full-screen", update);
|
WEBVIEW.window.on("enter-full-screen", update);
|
||||||
WEBVIEW.window.on("leave-full-screen", update);
|
WEBVIEW.window.on("leave-full-screen", update);
|
||||||
|
|
||||||
// Handle window touch events for activity tracking
|
// Handle global shortcut events
|
||||||
|
globalShortcut.register("Control+Left", () => {
|
||||||
|
previousView();
|
||||||
|
});
|
||||||
|
globalShortcut.register("Control+Right", () => {
|
||||||
|
nextView();
|
||||||
|
});
|
||||||
|
globalShortcut.register("Control+numsub", () => {
|
||||||
|
zoomMinus();
|
||||||
|
});
|
||||||
|
globalShortcut.register("Control+numadd", () => {
|
||||||
|
zoomPlus();
|
||||||
|
});
|
||||||
|
globalShortcut.register("Alt+Left", () => {
|
||||||
|
historyBackward();
|
||||||
|
});
|
||||||
|
globalShortcut.register("Alt+Right", () => {
|
||||||
|
historyForward();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for window touch events (1s)
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const then = WEBVIEW.pointer.time;
|
const then = WEBVIEW.tracker.pointer.time;
|
||||||
const delta = Math.abs(now - then) / 1000;
|
const delta = (now - then) / 1000;
|
||||||
const posNew = screen.getCursorScreenPoint();
|
|
||||||
const posOld = WEBVIEW.pointer.position;
|
|
||||||
|
|
||||||
// Cursor movement detected
|
// Auto-hide navigation
|
||||||
if (posOld.x !== posNew.x || posOld.y !== posNew.y) {
|
if (delta > 60) {
|
||||||
WEBVIEW.pointer.time = now;
|
|
||||||
|
|
||||||
// Update integration sensor
|
|
||||||
if (delta > 30) {
|
|
||||||
console.log("Update Last Active");
|
|
||||||
integration.update();
|
|
||||||
}
|
|
||||||
} else if (delta > 60) {
|
|
||||||
const navigation = WEBVIEW.navigation.getBounds();
|
const navigation = WEBVIEW.navigation.getBounds();
|
||||||
|
|
||||||
// Auto-hide navigation
|
|
||||||
if (navigation.height > 0) {
|
if (navigation.height > 0) {
|
||||||
toggleNavigation();
|
toggleNavigation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WEBVIEW.pointer.position = posNew;
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register widget events and handler.
|
* Register widget events and handler.
|
||||||
*/
|
*/
|
||||||
const widgetEvents = () => {
|
const widgetEvents = async () => {
|
||||||
if (!WEBVIEW.widgetEnabled) {
|
if (!WEBVIEW.widgetEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -406,7 +516,7 @@ const widgetEvents = () => {
|
|||||||
|
|
||||||
// Hide widget
|
// Hide widget
|
||||||
WEBVIEW.widget.setBounds({
|
WEBVIEW.widget.setBounds({
|
||||||
x: window.width - 15,
|
x: window.width - 20,
|
||||||
y: widget.y,
|
y: widget.y,
|
||||||
width: widget.width,
|
width: widget.width,
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
@@ -419,7 +529,6 @@ const widgetEvents = () => {
|
|||||||
switch (button.id) {
|
switch (button.id) {
|
||||||
case "keyboard":
|
case "keyboard":
|
||||||
const toggle = hardware.getKeyboardVisibility() === "ON" ? "OFF" : "ON";
|
const toggle = hardware.getKeyboardVisibility() === "ON" ? "OFF" : "ON";
|
||||||
hardware.setKeyboardVisibility(toggle);
|
|
||||||
switch (toggle) {
|
switch (toggle) {
|
||||||
case "OFF":
|
case "OFF":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
@@ -432,6 +541,9 @@ const widgetEvents = () => {
|
|||||||
WEBVIEW.window.maximize();
|
WEBVIEW.window.maximize();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
hardware.setKeyboardVisibility(toggle, () => {
|
||||||
|
WEBVIEW.views[WEBVIEW.viewActive].webContents.focus();
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "fullscreen":
|
case "fullscreen":
|
||||||
if (WEBVIEW.window.isFullScreen()) {
|
if (WEBVIEW.window.isFullScreen()) {
|
||||||
@@ -443,7 +555,9 @@ const widgetEvents = () => {
|
|||||||
WEBVIEW.window.unmaximize();
|
WEBVIEW.window.unmaximize();
|
||||||
WEBVIEW.window.setFullScreen(true);
|
WEBVIEW.window.setFullScreen(true);
|
||||||
}
|
}
|
||||||
hardware.setKeyboardVisibility("OFF");
|
hardware.setKeyboardVisibility("OFF", () => {
|
||||||
|
WEBVIEW.views[WEBVIEW.viewActive].webContents.focus();
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
case "minimize":
|
case "minimize":
|
||||||
WEBVIEW.window.restore();
|
WEBVIEW.window.restore();
|
||||||
@@ -461,7 +575,7 @@ const widgetEvents = () => {
|
|||||||
/**
|
/**
|
||||||
* Register navigation events and handler.
|
* Register navigation events and handler.
|
||||||
*/
|
*/
|
||||||
const navigationEvents = () => {
|
const navigationEvents = async () => {
|
||||||
if (!WEBVIEW.navigationEnabled) {
|
if (!WEBVIEW.navigationEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -519,13 +633,30 @@ const navigationEvents = () => {
|
|||||||
|
|
||||||
// Handle navigation button click events
|
// Handle navigation button click events
|
||||||
ipcMain.on("button-click", (e, button) => {
|
ipcMain.on("button-click", (e, button) => {
|
||||||
|
const view = WEBVIEW.views[WEBVIEW.viewActive];
|
||||||
switch (button.id) {
|
switch (button.id) {
|
||||||
case "pager":
|
case "home":
|
||||||
|
homeView();
|
||||||
|
hardware.setKeyboardVisibility("OFF");
|
||||||
|
break;
|
||||||
|
case "refresh":
|
||||||
|
reloadView();
|
||||||
|
hardware.setKeyboardVisibility("OFF");
|
||||||
|
break;
|
||||||
|
case "previous":
|
||||||
|
previousView();
|
||||||
|
hardware.setKeyboardVisibility("OFF");
|
||||||
|
break;
|
||||||
|
case "next":
|
||||||
nextView();
|
nextView();
|
||||||
hardware.setKeyboardVisibility("OFF");
|
hardware.setKeyboardVisibility("OFF");
|
||||||
break;
|
break;
|
||||||
case "home":
|
case "minus":
|
||||||
reloadView();
|
zoomMinus();
|
||||||
|
hardware.setKeyboardVisibility("OFF");
|
||||||
|
break;
|
||||||
|
case "plus":
|
||||||
|
zoomPlus();
|
||||||
hardware.setKeyboardVisibility("OFF");
|
hardware.setKeyboardVisibility("OFF");
|
||||||
break;
|
break;
|
||||||
case "backward":
|
case "backward":
|
||||||
@@ -541,10 +672,11 @@ const navigationEvents = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register dom events and handler.
|
* Register view events and handler.
|
||||||
*/
|
*/
|
||||||
const domEvents = () => {
|
const viewEvents = async () => {
|
||||||
WEBVIEW.views.forEach((view) => {
|
const ready = [];
|
||||||
|
WEBVIEW.views.forEach((view, i) => {
|
||||||
// Enable webview touch emulation
|
// Enable webview touch emulation
|
||||||
view.webContents.debugger.attach("1.1");
|
view.webContents.debugger.attach("1.1");
|
||||||
view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", {
|
view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", {
|
||||||
@@ -558,15 +690,18 @@ const domEvents = () => {
|
|||||||
return { action: "deny" };
|
return { action: "deny" };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove webview scrollbar
|
// Update webview layout
|
||||||
view.webContents.on("dom-ready", () => {
|
view.webContents.on("dom-ready", () => {
|
||||||
view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
|
view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
|
||||||
|
if (ready.length < WEBVIEW.views.length) {
|
||||||
|
view.webContents.setZoomFactor(WEBVIEW.viewZoom);
|
||||||
|
ready.push(i);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Webview fully loaded
|
// Webview fully loaded
|
||||||
view.webContents.on("did-finish-load", () => {
|
view.webContents.on("did-finish-load", () => {
|
||||||
WEBVIEW.initialized = true;
|
if (WEBVIEW.viewActive === 0 && ready.length === WEBVIEW.views.length) {
|
||||||
if (WEBVIEW.viewActive === 0) {
|
|
||||||
nextView();
|
nextView();
|
||||||
}
|
}
|
||||||
if (ARGS.app_debug === "true") {
|
if (ARGS.app_debug === "true") {
|
||||||
@@ -579,7 +714,7 @@ const domEvents = () => {
|
|||||||
// Webview not loaded
|
// Webview not loaded
|
||||||
view.webContents.on("did-fail-load", (e, code, text, url, mainframe) => {
|
view.webContents.on("did-fail-load", (e, code, text, url, mainframe) => {
|
||||||
if (mainframe) {
|
if (mainframe) {
|
||||||
if (WEBVIEW.viewActive === 0) {
|
if (WEBVIEW.viewActive === 0 && ready.length === WEBVIEW.views.length) {
|
||||||
nextView();
|
nextView();
|
||||||
}
|
}
|
||||||
view.webContents.loadURL(errorHtml(code, text, url));
|
view.webContents.loadURL(errorHtml(code, text, url));
|
||||||
@@ -589,11 +724,57 @@ const domEvents = () => {
|
|||||||
// Webview url changed
|
// Webview url changed
|
||||||
view.webContents.on("did-navigate-in-page", (e, url, mainframe) => {
|
view.webContents.on("did-navigate-in-page", (e, url, mainframe) => {
|
||||||
if (mainframe) {
|
if (mainframe) {
|
||||||
update();
|
updateView();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
view.webContents.on("did-navigate", () => {
|
view.webContents.on("did-navigate", () => {
|
||||||
update();
|
updateView();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle webview mouse events
|
||||||
|
view.webContents.on("before-mouse-event", (e, mouse) => {
|
||||||
|
const now = new Date();
|
||||||
|
const then = WEBVIEW.tracker.pointer.time;
|
||||||
|
const delta = (now - then) / 1000;
|
||||||
|
switch (mouse.type) {
|
||||||
|
case "mouseMove":
|
||||||
|
const posNew = { x: mouse.globalX, y: mouse.globalY };
|
||||||
|
if (posNew.x < 0 || posNew.y < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Update last active on pointer position change
|
||||||
|
const posOld = WEBVIEW.tracker.pointer.position;
|
||||||
|
if (posOld.x !== posNew.x || posOld.y !== posNew.y) {
|
||||||
|
WEBVIEW.tracker.pointer.time = now;
|
||||||
|
WEBVIEW.tracker.pointer.position = posNew;
|
||||||
|
if (delta > 30) {
|
||||||
|
console.log("Update Last Active");
|
||||||
|
integration.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "mouseDown":
|
||||||
|
switch (mouse.button) {
|
||||||
|
case "left":
|
||||||
|
// Ignore touch event if display was off
|
||||||
|
if (WEBVIEW.tracker.display.off > WEBVIEW.tracker.display.on) {
|
||||||
|
console.log("Display Touch Event: Ignored");
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
// Turn display on if it was off
|
||||||
|
if (hardware.getDisplayStatus() === "OFF") {
|
||||||
|
hardware.setDisplayStatus("ON");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "back":
|
||||||
|
historyBackward();
|
||||||
|
break;
|
||||||
|
case "forward":
|
||||||
|
historyForward();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -601,14 +782,7 @@ const domEvents = () => {
|
|||||||
/**
|
/**
|
||||||
* Register app events and handler.
|
* Register app events and handler.
|
||||||
*/
|
*/
|
||||||
const appEvents = () => {
|
const appEvents = async () => {
|
||||||
// Handle signal and exit events
|
|
||||||
process.on("SIGINT", app.quit);
|
|
||||||
app.on("before-quit", () => {
|
|
||||||
WEBVIEW.status = "Terminated";
|
|
||||||
integration.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle multiple instances
|
// Handle multiple instances
|
||||||
app.on("second-instance", () => {
|
app.on("second-instance", () => {
|
||||||
if (WEBVIEW.window.isMinimized()) {
|
if (WEBVIEW.window.isMinimized()) {
|
||||||
@@ -616,6 +790,77 @@ const appEvents = () => {
|
|||||||
}
|
}
|
||||||
WEBVIEW.window.focus();
|
WEBVIEW.window.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Handle signal and exit events
|
||||||
|
app.on("before-quit", () => {
|
||||||
|
WEBVIEW.tracker.status = "Terminated";
|
||||||
|
console.warn(`${APP.title} Terminated`);
|
||||||
|
integration.update();
|
||||||
|
});
|
||||||
|
process.on("SIGINT", app.quit);
|
||||||
|
|
||||||
|
// Webview initialized
|
||||||
|
WEBVIEW.initialized = true;
|
||||||
|
|
||||||
|
// Check for latest release infos (2h)
|
||||||
|
setInterval(() => {
|
||||||
|
latestRelease();
|
||||||
|
}, 7200 * 1000);
|
||||||
|
await latestRelease();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the latest app release infos from github.
|
||||||
|
*/
|
||||||
|
const latestRelease = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${APP.releases.url}/latest`, { timeout: 10000 });
|
||||||
|
const data = response ? response.data : null;
|
||||||
|
if (!data || data.draft || data.prerelease) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
APP.releases.latest = {
|
||||||
|
title: APP.title,
|
||||||
|
version: (data.tag_name || data.name || "").replace(/^v/i, ""),
|
||||||
|
summary: data.body || "",
|
||||||
|
url: data.html_url || "",
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.warn("Github Error:", error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for network connectivity by requesting a known url.
|
||||||
|
*
|
||||||
|
* @param {string} url - Url to request.
|
||||||
|
* @param {number} interval - Interval between requests in milliseconds.
|
||||||
|
* @param {number} timeout - Maximum time to repeat requests in milliseconds.
|
||||||
|
* @returns {Promise<boolean>} Resolves true if online, false on timeout.
|
||||||
|
*/
|
||||||
|
const onlineStatus = (url, interval = 1000, timeout = 60000) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const start = Date.now();
|
||||||
|
const check = async () => {
|
||||||
|
const elapsed = Date.now() - start;
|
||||||
|
try {
|
||||||
|
if (!url.startsWith("data:")) {
|
||||||
|
await axios.get(url, { timeout: 10000 });
|
||||||
|
}
|
||||||
|
resolve(true);
|
||||||
|
} catch (error) {
|
||||||
|
if (elapsed >= interval) {
|
||||||
|
console.warn(`Checking Connection: ${url}`, error.message);
|
||||||
|
}
|
||||||
|
if (elapsed >= timeout) {
|
||||||
|
resolve(false);
|
||||||
|
} else {
|
||||||
|
setTimeout(check, interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
check();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+12
-8
@@ -1,27 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "touchkio",
|
"name": "touchkio",
|
||||||
|
"title": "TouchKio",
|
||||||
"description": "Home Assistant Touch Kiosk",
|
"description": "Home Assistant Touch Kiosk",
|
||||||
"homepage": "https://github.com/leukipp/touchkio",
|
"homepage": "https://github.com/leukipp/touchkio",
|
||||||
"author": "leukipp",
|
"author": "leukipp",
|
||||||
"version": "1.2.0",
|
"version": "1.3.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron . --web-url=https://demo.home-assistant.io --trace-warnings",
|
"start": "electron . --web-url=https://demo.home-assistant.io,https://demo.immichkiosk.app --app-debug=false --trace-warnings",
|
||||||
"build": "electron-forge make"
|
"build": "electron-forge make"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 120
|
"printWidth": 120
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-forge/cli": "^7.8.1",
|
"@electron-forge/cli": "^7.8.3",
|
||||||
"@electron-forge/maker-deb": "^7.8.1",
|
"@electron-forge/maker-deb": "^7.8.3",
|
||||||
"@electron-forge/maker-zip": "^7.8.1",
|
"@electron-forge/maker-zip": "^7.8.3",
|
||||||
"@electron-forge/publisher-github": "^7.8.1",
|
"@electron-forge/publisher-github": "^7.8.3",
|
||||||
"electron": "^37.2.0",
|
"electron": "^38.0.0",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
"prettier": "^3.6.2"
|
"prettier": "^3.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mqtt": "^5.13.2"
|
"axios": "^1.11.0",
|
||||||
|
"electron-log": "^5.4.3",
|
||||||
|
"mqtt": "^5.14.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user