Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aed79ab519 | ||
|
|
e2397bad65 | ||
|
|
e7622bef17 | ||
|
|
2553f52983 | ||
|
|
30b5ec2d8b | ||
|
|
94c7e34706 | ||
|
|
09dda65333 | ||
|
|
99f465a044 | ||
|
|
02d1309345 | ||
|
|
3142606d12 | ||
|
|
51189e8595 | ||
|
|
71a9d4c1de | ||
|
|
f022db9478 | ||
|
|
fcc0e5813e | ||
|
|
6d23efd713 | ||
|
|
a37226fd39 | ||
|
|
9b8067a598 | ||
|
|
257dab8d7f | ||
|
|
ff4b9d1262 | ||
|
|
757ba9ff92 | ||
|
|
ed3bad7292 | ||
|
|
f00a49e4e4 | ||
|
|
bbe20fef8f |
+35
-19
@@ -1,34 +1,50 @@
|
||||
# TouchKio - Supported Hardware
|
||||
This document contains an incomplete list of device and display hardware combinations that are either intended to be tested or have already been tested.
|
||||
# Supported Hardware
|
||||
This document contains an incomplete list of device and display hardware combinations that have already been tested.
|
||||
|
||||
The [release](https://github.com/leukipp/touchkio/releases) page has builds exclusively for **arm64** and **x64**, but custom builds for other architectures can be made (see [Development](https://github.com/leukipp/touchkio?tab=readme-ov-file#development)), allowing the application to operate on any hardware.
|
||||
The [release](https://github.com/leukipp/touchkio/releases) page has builds exclusively for **arm64** and **x64**, but custom builds for other architectures can be made (see [development](https://github.com/leukipp/touchkio?tab=readme-ov-file#development)), allowing the application to operate on any hardware.
|
||||
At least the **minimal** features, such as displaying a **kiosk window** (which doesn't necessarily need to be Home Assistant) will work.
|
||||
|
||||
## Hardware
|
||||
If you are running Linux with a graphical user interface (Wayland or X11), you should be well equipped to use the application. Additionally, any single board computer (SBC) clones of the Raspberry Pi that operate on Raspberry Pi OS **(64-bit)** are likely to function properly as well.
|
||||
If you are running Linux with a graphical user interface (Wayland or X11), you should be well equipped to use the application. Additionally, any single board computer (SBC) clones of the Raspberry Pi that operate on Raspberry Pi OS **(64-bit)** are likely to function as well.
|
||||
|
||||
| | Status | Notes |
|
||||
| --- | --------------------- | ------------------------------------------------------------------------- |
|
||||
| 🟩 | Fully operational | Working display power, brightness and keyboard control via MQTT. |
|
||||
| 🟨 | Partially operational | Display brightness control is not available via MQTT. |
|
||||
| 🟧 | Partially operational | Display brightness and keyboard control is not available via MQTT. |
|
||||
| 🟥 | Partially operational | Display power, brightness and keyboard control is not available via MQTT. |
|
||||
| ⬜ | Somehow operational | Issues can occur and the overall performance is very slow. |
|
||||
| ⬛ | Not operational | The house is on fire. |
|
||||
|
||||
### DSI
|
||||
| Device | Display | Tested | Status | Notes |
|
||||
| -------------- | ----------------------------------------------------------------------------------------------- | ------ | ----------------- | ---------------------------------------------------------------- |
|
||||
| Raspberry Pi 3 | [Official 7" Touch Display 1](https://www.raspberrypi.com/products/raspberry-pi-touch-display/) | ❌ | Unknown | 99% chance that it is slow and the resolution sucks. |
|
||||
| Raspberry Pi 3 | [Official 7" Touch Display 2](https://www.raspberrypi.com/products/touch-display-2/) | ❌ | Unknown | 99% chance that it is slow. |
|
||||
| Raspberry Pi 4 | [Official 7" Touch Display 1](https://www.raspberrypi.com/products/raspberry-pi-touch-display/) | ❌ | Unknown | 99% chance that the resolution sucks. |
|
||||
| Raspberry Pi 4 | [Official 7" Touch Display 2](https://www.raspberrypi.com/products/touch-display-2/) | ❌ | Optimistic | 99% chance that it fully works. |
|
||||
| Raspberry Pi 5 | [Official 7" Touch Display 1](https://www.raspberrypi.com/products/raspberry-pi-touch-display/) | ❌ | Unknown | 99% chance that the resolution sucks. |
|
||||
| Raspberry Pi 5 | [Official 7" Touch Display 2](https://www.raspberrypi.com/products/touch-display-2/) | ✅ | Fully operational | Working display power, brightness and keyboard control via MQTT. |
|
||||
| Device | System | Display | Status |
|
||||
| ---------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------ |
|
||||
| Raspberry Pi 3 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Official 7" Touch Display 1 (800x480)](https://www.raspberrypi.com/products/raspberry-pi-touch-display/) | ⬜ |
|
||||
| Raspberry Pi 3 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Official 7" Touch Display 2 (720x1280)](https://www.raspberrypi.com/products/touch-display-2/) | ⬜ |
|
||||
| Raspberry Pi 4 (arm64) | Raspberry Pi OS (64-bit), Wayland, X11 | [Official 7" Touch Display 1 (800x480)](https://www.raspberrypi.com/products/raspberry-pi-touch-display/) | 🟩 |
|
||||
| Raspberry Pi 4 (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 1 (800x480)](https://www.raspberrypi.com/products/raspberry-pi-touch-display/) | 🟩 |
|
||||
| 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
|
||||
| Device | Display | Tested | Status | Notes |
|
||||
| -------------- | ---------------------------------------------------------------------- | ------ | --------------------- | ------------------------------------------------------------------ |
|
||||
| Raspberry Pi 4 | [LAFVIN 5" Touch Display](https://www.amazon.de/gp/product/B0BWJ8YP7S) | ✅ | Partially operational | Display brightness control is not available via MQTT. |
|
||||
| Raspberry Pi 5 | Generic Non-Touch | ✅ | Partially operational | Display brightness control is not available via MQTT. |
|
||||
| Generic Ubuntu | Generic Non-Touch | ✅ | Partially operational | Display brightness and keyboard control is not available via MQTT. |
|
||||
| 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 | [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 | [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 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 | [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 | 🟨 |
|
||||
| Generic PC (x64) | Ubuntu (64-bit), X11 | Generic Non-Touch | 🟧 |
|
||||
| Generic PC (x64) | Ubuntu (64-bit), Wayland | Generic Non-Touch | 🟥 |
|
||||
|
||||
## Contributions
|
||||
In case your hardware is not listed above don't worry, give it a try.
|
||||
Running `touchkio --web-url=https://demo.home-assistant.io` will most likely just work.
|
||||
The only problems that may arise are when controlling the display via the Home Assistant integration.
|
||||
The only problems that may arise are when controlling the display or keyboard via the Home Assistant integration.
|
||||
|
||||
- If you encounter any problems, please create a new [issue](https://github.com/leukipp/touchkio/issues).
|
||||
- If you encounter any problems and are able to fix it yourself, feel free to create a [pull request](https://github.com/leukipp/touchkio/pulls).
|
||||
- If everything works as expected and your hardware is not yet listed, you are welcome to add it and create a [pull request](https://github.com/leukipp/touchkio/pulls).
|
||||
- If everything works as expected and your hardware is not yet listed, you are welcome to [report](https://github.com/leukipp/touchkio/issues/12) it or create a [pull request](https://github.com/leukipp/touchkio/pulls).
|
||||
|
||||
@@ -11,6 +11,7 @@ Moreover, the device running the **kiosk application** also offers several **Hom
|
||||
## Features
|
||||
- [x] Fast and easy setup.
|
||||
- [x] Remember login credentials.
|
||||
- [x] Side panel widget for kiosk control.
|
||||
- [x] Touch friendly web browser interface.
|
||||
- [x] Configurable zoom and theme support.
|
||||
- [x] Single-tap screen wake-up functionality.
|
||||
@@ -27,8 +28,8 @@ Additionally, a **MQTT endpoint** can be defined, allowing the application to pr
|
||||
|
||||
## Setup
|
||||
Before you begin, make sure that you have a Linux device configured and operational with a [compatible](https://github.com/leukipp/touchkio/blob/main/HARDWARE.md) Touch Display.
|
||||
This guide assumes that you are using a Raspberry Pi with the latest version of Raspberry Pi OS **(64-bit)**, along with a desktop environment (preferred using labwc).
|
||||
However, the **.deb** setup is also compatible with any other Debian based system.
|
||||
This guide assumes that you are using a Raspberry Pi with the latest version of Raspberry Pi OS **(64-bit)**, along with a desktop environment (preferred using **labwc**).
|
||||
However, the **.deb** setup procedure is also compatible with any other Debian based 64-bit system.
|
||||
|
||||
### Optional
|
||||
To utilize the sensor features of your device through Home Assistant, it's essential to have a **MQTT broker running** and the **MQTT integration installed** on your Home Assistant instance.
|
||||
@@ -41,19 +42,30 @@ For a comprehensive guide on setting up MQTT with Home Assistant, please refer t
|
||||
## Installation
|
||||
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.
|
||||
You might also need a 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).
|
||||
|
||||
#### Option 1 - The easy way
|
||||
Run this command to download and install the latest **.deb** (arm64 or x64) release.
|
||||
It will also create a systemd file for auto-startup and will guide you through the setup process:
|
||||
It will also create a systemd user file for auto-startup and will guide you through the setup process:
|
||||
```bash
|
||||
bash <(wget -qO- https://raw.githubusercontent.com/leukipp/touchkio/main/install.sh)
|
||||
```
|
||||
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.
|
||||
The script 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.
|
||||
If you need manual control use:
|
||||
```bash
|
||||
systemctl --user start|stop|status|restart touchkio.service
|
||||
```
|
||||
|
||||
<details><summary>Alternatives</summary><div>
|
||||
|
||||
#### Option 2 - The standard way
|
||||
When connected via SSH, it's necessary to export the display variables first, as outlined in the [development](https://github.com/leukipp/touchkio?tab=readme-ov-file#development) section.
|
||||
The [install.sh](https://github.com/leukipp/touchkio/blob/main/install.sh) script mentioned above performs the following tasks (and you just have to do it manually):
|
||||
- [Download](https://github.com/leukipp/touchkio/releases/latest) the latest version file that is suitable for your architecture (arm64 or x64).
|
||||
- Debian (**deb**): Open a terminal and execute the following command to install the application, e.g:
|
||||
@@ -68,7 +80,7 @@ The [install.sh](https://github.com/leukipp/touchkio/blob/main/install.sh) scrip
|
||||
Pre-built release files are available for arm64 and x64 Linux systems.
|
||||
If you are using a different architecture, you can still utilize this repository to build your own application.
|
||||
|
||||
For more information, please refer to the [development](https://github.com/leukipp/touchkio?tab=readme-ov-file#development) section. However this could do the job:
|
||||
For more information please refer to the [development](https://github.com/leukipp/touchkio?tab=readme-ov-file#development) section, however this will do the job:
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
@@ -82,16 +94,17 @@ These default arguments are stored in `~/.config/touchkio/Arguments.json`, where
|
||||
### WEB
|
||||
The available arguments to control the kiosk application via terminal are as follows:
|
||||
| Name | Default | Description |
|
||||
| ------------------------ | ------- | ------------------------------------------------------ |
|
||||
| ------------------------- | ------- | ------------------------------------------------------ |
|
||||
| `--web-url` (Required) | - | Url of the Home Assistant instance (HTTP(S)://IP:PORT) |
|
||||
| `--web-theme` (Optional) | `dark` | Theme settings of the web browser (`light` or `dark`) |
|
||||
| `--web-zoom` (Optional) | `1.25` | Zoom settings of the web browser (`1.0` is `100%`) |
|
||||
| `--web-widget` (Optional) | `true` | Enables the sidebar widget (`true` or `false`) |
|
||||
|
||||
These arguments allow you to customize the appearance of the web browser window.
|
||||
These arguments allow you to customize the appearance of the web browser view.
|
||||
|
||||
For example:
|
||||
```bash
|
||||
touchkio --web-url=http://192.168.1.42:8123 --web-theme=dark --web-zoom=1.25
|
||||
touchkio --web-url=http://192.168.1.42:8123 --web-theme=light --web-zoom=1.0
|
||||
```
|
||||
|
||||
### MQTT
|
||||
@@ -115,7 +128,7 @@ touchkio --web-url=http://192.168.1.42:8123 --mqtt-url=mqtt://192.168.1.42:1883
|
||||
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).
|
||||
|
||||
Clone this repository and run `yarn install` to install the dependencies.
|
||||
Then use `yarn start` to execute the start script located in [package.json](https://github.com/leukipp/touchkio/blob/main/package.json) file.
|
||||
Then use `yarn start` to execute the start script located in the [package.json](https://github.com/leukipp/touchkio/blob/main/package.json) file.
|
||||
There you can adjust the `--web-url` and other arguments for development runs.
|
||||
|
||||
If you connect to your device via SSH, you may have to export display variables so that the kiosk application can be loaded inside the desktop environment:
|
||||
@@ -123,6 +136,42 @@ If you connect to your device via SSH, you may have to export display variables
|
||||
export DISPLAY=":0"
|
||||
export WAYLAND_DISPLAY="wayland-0"
|
||||
```
|
||||
To make this permanent, consider adding the export variables into the `~/.bashrc` file.
|
||||
|
||||
### Extensions
|
||||
|
||||
<details><summary>You probably won't need this.</summary><div></br>
|
||||
|
||||
Incorporating custom extensions and external hardware (like motion sensors, ultrasonic sensors, cameras, relays and switches) via **Raspberry Pi's GPIO/USB** involves several steps.
|
||||
While using external sensors that directly integrate with Home Assistant and by utilizing automation's to interact with **TouchKio via MQTT** is generally easier and **recommended**, here's a rough guide on how to proceed with custom hardware integration:
|
||||
|
||||
1. **Install Node.js library**: Use Yarn to add a library that can interact with your hardware (GPIO, USB, etc.):
|
||||
```bash
|
||||
yarn add [package-name]
|
||||
```
|
||||
This will update the `package.json` file with the required dependencies.
|
||||
|
||||
2. **Import the library**: Open the `hardware.js` file and import the library using:
|
||||
```javascript
|
||||
const package = require("[package-name]");
|
||||
```
|
||||
Then implement your custom methods and logic to handle the hardware. Don't forget to export the methods at the end of the file:
|
||||
```javascript
|
||||
module.exports = { ... };
|
||||
```
|
||||
|
||||
3. **Expose sensors via MQTT**: If you want to publish sensor data through MQTT, implement some init and update methods in the `integration.js` file:
|
||||
```javascript
|
||||
const initCustomSensor = (client) => { ... }
|
||||
const updateCustomSensor = (client) => { ... }
|
||||
```
|
||||
To get started have a look at the existing methods. Don't forget to call the custom sensor initialization method inside the global init method, where the MQTT connection is established:
|
||||
```javascript
|
||||
const init = async (args) => { ... }
|
||||
```
|
||||
From there, you will need to further refine your code by tinkering with sensor updates. This can be achieved through either periodic update calls or event based triggers.
|
||||
|
||||
</div></details>
|
||||
|
||||
### The nitty gritty
|
||||
|
||||
@@ -140,7 +189,7 @@ I managed to achieve this for the `brightness` file by implementing a simple `fs
|
||||
However, I found that it **never triggered** for the `bl_power` file.
|
||||
Although the file content changes, none of the filesystem listeners where fired.
|
||||
This could be due to `swayidle`/`wlopm` performing write actions at a deeper level that are not detectable by file listeners.
|
||||
As a result, I went for a **polling solution**, checking the state of the file every **500 milliseconds** for any changes.
|
||||
As a result, I went for a **polling solution**, checking the state of the file **every second** for any changes.
|
||||
While I understand this is not ideal, it's necessary to ensure proper functionality.
|
||||
|
||||
The display power status and brightness can be adjusted via the MQTT integration.
|
||||
@@ -172,15 +221,18 @@ Please have a look into the [hardware](https://github.com/leukipp/touchkio/blob/
|
||||
- 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:
|
||||
- Sensor features that may work out of the box include those that do not require any interaction with the display or keyboard.
|
||||
- Sensor features that may work out-of-the-box include those that do not require any interaction with the display or keyboard.
|
||||
- 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.
|
||||
|
||||
**Known Issues** that are by-design or for which there is so far no solution:
|
||||
**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.
|
||||
- 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.
|
||||
- This [issue](https://github.com/leukipp/touchkio/issues/4) also occurs in the default browser but as a current workaround you can use the side [widget](https://github.com/leukipp/touchkio/issues/16) to toggle the keyboard.
|
||||
- Hyperlinks that redirect the browser away from the main window are intentionally disabled.
|
||||
- This decision was made to maintain a clean design, as navigation buttons to move back and forth between different sites were not included.
|
||||
- On Raspberry Pi's 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.
|
||||
|
||||
## Credits
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 154 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 59 KiB |
@@ -7,6 +7,16 @@ const hardware = require("./src/hardware");
|
||||
const webview = require("./src/webview");
|
||||
const { app } = require("electron");
|
||||
|
||||
global.ARGS = global.ARGS || {};
|
||||
|
||||
if (!process.env.DISPLAY) {
|
||||
console.error(`$DISPLAY variable not set to run the GUI application, are you connected via SSH?`);
|
||||
console.error(`If you have installed the service use:`);
|
||||
console.error(` systemctl --user start touchkio.service`);
|
||||
console.error(`Alternatively export the variables first:`);
|
||||
console.error(` export DISPLAY=":0" && export WAYLAND_DISPLAY="wayland-0" && touchkio\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* This promise resolves when the app has finished initializing,
|
||||
* allowing to safely create browser windows and perform other
|
||||
@@ -34,15 +44,15 @@ app.whenReady().then(async () => {
|
||||
} else if (!argsProvided && argsFileExists) {
|
||||
args = readArgs(argsFilePath);
|
||||
}
|
||||
global.ARGS = args;
|
||||
|
||||
// Show used arguments
|
||||
const json = JSON.stringify(args, null, 2);
|
||||
console.log(`Arguments: ${json}`);
|
||||
console.log(`Arguments: ${JSON.stringify(global.ARGS, null, 2)}`);
|
||||
|
||||
// Chained init functions
|
||||
const chained = [webview.init, hardware.init, integration.init];
|
||||
for (const init of chained) {
|
||||
if (!(await init(args))) {
|
||||
if (!(await init())) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -94,6 +104,11 @@ const promptArgs = async (proc) => {
|
||||
question: "Enter WEB zoom level",
|
||||
fallback: "1.25",
|
||||
},
|
||||
{
|
||||
key: "web_widget",
|
||||
question: "Enter WEB widget enabled",
|
||||
fallback: "true",
|
||||
},
|
||||
{
|
||||
key: "mqtt",
|
||||
question: "\nConnect to MQTT Broker?",
|
||||
@@ -223,4 +238,6 @@ const decrypt = (value) => {
|
||||
* @param {number} ms - Sleep time in milliseconds.
|
||||
* @returns {Promise} A promise resolving after the timeout.
|
||||
*/
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
const sleep = (ms) => {
|
||||
return new Promise((r) => setTimeout(r, ms));
|
||||
};
|
||||
|
||||
+15
-3
@@ -3,6 +3,7 @@
|
||||
# Determine system architecture
|
||||
echo -e "Determining system architecture..."
|
||||
|
||||
BITS=$(getconf LONG_BIT)
|
||||
case "$(uname -m)" in
|
||||
aarch64)
|
||||
ARCH="arm64"
|
||||
@@ -11,11 +12,17 @@ case "$(uname -m)" in
|
||||
ARCH="x64"
|
||||
;;
|
||||
*)
|
||||
echo "System architecture $(uname -m) not supported."
|
||||
echo "Architecture $(uname -m) running $BITS-bit operating system is not supported."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
echo "System architecture $ARCH is supported."
|
||||
|
||||
if [ "$BITS" -ne 64 ]; then
|
||||
echo "Architecture $ARCH running $BITS-bit operating system is not supported."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Architecture $ARCH running $BITS-bit operating system is supported."
|
||||
|
||||
# Download the latest .deb package
|
||||
echo -e "\nDownloading the latest release..."
|
||||
@@ -23,7 +30,9 @@ echo -e "\nDownloading the latest release..."
|
||||
DEB_URL=$(wget -qO- https://api.github.com/repos/leukipp/touchkio/releases/latest | \
|
||||
grep -o "\"browser_download_url\": \"[^\"]*_${ARCH}\.deb\"" | \
|
||||
sed 's/"browser_download_url": "//;s/"//g')
|
||||
DEB_PATH="/tmp/$(basename "$DEB_URL")"
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
DEB_PATH="${TMP_DIR}/$(basename "$DEB_URL")"
|
||||
|
||||
if [ -z "$DEB_URL" ]; then
|
||||
echo "Download url for .deb file not found."
|
||||
@@ -93,4 +102,7 @@ read -p "Start touchkio setup? (Y/n) " answer
|
||||
if [[ ${answer:-y} == [Yy]* ]]; then
|
||||
echo "/usr/bin/touchkio --setup"
|
||||
/usr/bin/touchkio --setup
|
||||
else
|
||||
echo "/usr/bin/touchkio"
|
||||
/usr/bin/touchkio
|
||||
fi
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"description": "Home Assistant Touch Kiosk",
|
||||
"homepage": "https://github.com/leukipp/touchkio",
|
||||
"author": "leukipp",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.2",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
+66
-29
@@ -1,11 +1,13 @@
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const process = require("child_process");
|
||||
const fsp = require("fs/promises");
|
||||
const cpr = require("child_process");
|
||||
|
||||
global.HARDWARE = global.HARDWARE || {
|
||||
initialized: false,
|
||||
status: "invalid",
|
||||
status: null,
|
||||
session: {},
|
||||
support: {},
|
||||
display: {
|
||||
status: {
|
||||
@@ -28,33 +30,32 @@ global.HARDWARE = global.HARDWARE || {
|
||||
/**
|
||||
* Initializes the hardware with the provided arguments.
|
||||
*
|
||||
* @param {Object} args - The command-line arguments to customize the initialization process.
|
||||
* @returns {boolean} Returns true if the initialization was successful.
|
||||
*/
|
||||
const init = async (args) => {
|
||||
if (!args.mqtt_url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check device support
|
||||
const init = async () => {
|
||||
if (!compatibleDevice()) {
|
||||
console.warn("Device not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Init globals
|
||||
HARDWARE.support.session = sessionType();
|
||||
HARDWARE.session.user = sessionUser();
|
||||
HARDWARE.session.type = sessionType();
|
||||
HARDWARE.display.status.path = getDisplayStatusPath();
|
||||
HARDWARE.support.displayStatus = HARDWARE.display.status.path !== null;
|
||||
HARDWARE.display.brightness.path = getDisplayBrightnessPath();
|
||||
HARDWARE.support.displayBrightness = HARDWARE.display.brightness.path !== null;
|
||||
HARDWARE.display.brightness.max = getDisplayBrightnessMax();
|
||||
HARDWARE.support.keyboardVisibility = processRuns("squeekboard");
|
||||
HARDWARE.initialized = true;
|
||||
HARDWARE.status = "valid";
|
||||
HARDWARE.status = "Supported";
|
||||
|
||||
// Show supported features
|
||||
console.log(`Supported: ${JSON.stringify(HARDWARE.support, null, 2)}`);
|
||||
|
||||
// Show session infos
|
||||
console.log("\nUser:", os.userInfo().username);
|
||||
console.log("Session:", HARDWARE.support.session);
|
||||
console.log("\nUser:", HARDWARE.session.user);
|
||||
console.log("Session:", HARDWARE.session.type);
|
||||
|
||||
// Show device infos
|
||||
console.log("\nModel:", getModel());
|
||||
@@ -73,8 +74,7 @@ const init = async (args) => {
|
||||
console.log(`\nDisplay Status [${HARDWARE.display.status.path}]:`, getDisplayStatus());
|
||||
console.log(`Display Brightness [${HARDWARE.display.brightness.path}]:`, getDisplayBrightness(), "\n");
|
||||
|
||||
// Check for keyboard visibility changes
|
||||
HARDWARE.support.keyboardVisibility = processRuns("squeekboard");
|
||||
// Check for keyboard visibility
|
||||
setKeyboardVisibility("OFF", (reply, error) => {
|
||||
if (!reply || error) {
|
||||
return;
|
||||
@@ -91,7 +91,7 @@ const init = async (args) => {
|
||||
});
|
||||
|
||||
// Check for display changes
|
||||
setInterval(update, 500);
|
||||
interval(update, 1000);
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -99,14 +99,14 @@ const init = async (args) => {
|
||||
/**
|
||||
* Updates the shared hardware properties.
|
||||
*/
|
||||
const update = () => {
|
||||
const update = async () => {
|
||||
if (!HARDWARE.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Display status has changed
|
||||
if (HARDWARE.support.displayStatus) {
|
||||
const status = fs.readFileSync(`${HARDWARE.display.status.path}/dpms`, "utf8").trim();
|
||||
const status = (await fsp.readFile(`${HARDWARE.display.status.path}/dpms`, "utf8")).trim();
|
||||
if (status !== HARDWARE.display.status.value) {
|
||||
console.log("Update Display Status:", getDisplayStatus());
|
||||
HARDWARE.display.status.value = status;
|
||||
@@ -118,7 +118,7 @@ const update = () => {
|
||||
|
||||
// Display brightness has changed
|
||||
if (HARDWARE.support.displayBrightness) {
|
||||
const brightness = fs.readFileSync(`${HARDWARE.display.brightness.path}/brightness`, "utf8").trim();
|
||||
const brightness = (await fsp.readFile(`${HARDWARE.display.brightness.path}/brightness`, "utf8")).trim();
|
||||
if (brightness !== HARDWARE.display.brightness.value) {
|
||||
console.log("Update Display Brightness:", getDisplayBrightness());
|
||||
HARDWARE.display.brightness.value = brightness;
|
||||
@@ -143,9 +143,21 @@ const compatibleDevice = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Queries the session type for the logged in user.
|
||||
* Gets the session user name using `os.userInfo()`.
|
||||
*
|
||||
* @returns {string} Returns session type 'x11'/'wayland' or null if an error occurs.
|
||||
* @returns {string|null} Returns session user name or null if an error occurs.
|
||||
*/
|
||||
const sessionUser = () => {
|
||||
try {
|
||||
return os.userInfo().username;
|
||||
} catch {}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the session type for the user using `loginctl`.
|
||||
*
|
||||
* @returns {string|null} Returns session type 'x11'/'wayland' or null if an error occurs.
|
||||
*/
|
||||
const sessionType = () => {
|
||||
if (!commandExists("loginctl")) {
|
||||
@@ -164,7 +176,7 @@ const sessionType = () => {
|
||||
* @returns {bool} Returns true if the session matches the type.
|
||||
*/
|
||||
const session = (type) => {
|
||||
return HARDWARE.support.session == type;
|
||||
return HARDWARE.session.type == type;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -350,7 +362,7 @@ const setDisplayStatus = (status, callback = null) => {
|
||||
if (typeof callback === "function") callback(null, "Not supported");
|
||||
return;
|
||||
}
|
||||
if (status !== "ON" && status !== "OFF") {
|
||||
if (!["ON", "OFF"].includes(status)) {
|
||||
console.error("Status must be 'ON' or 'OFF'");
|
||||
if (typeof callback === "function") callback(null, "Invalid status");
|
||||
return;
|
||||
@@ -464,6 +476,11 @@ const setKeyboardVisibility = (visibility, callback = null) => {
|
||||
if (typeof callback === "function") callback(null, "Not supported");
|
||||
return;
|
||||
}
|
||||
if (!["ON", "OFF"].includes(visibility)) {
|
||||
console.error("Visibility must be 'ON' or 'OFF'");
|
||||
if (typeof callback === "function") callback(null, "Invalid visibility");
|
||||
return;
|
||||
}
|
||||
const visible = visibility === "ON";
|
||||
HARDWARE.keyboard.visibility = visible;
|
||||
dbusCall("/sm/puri/OSK0", "SetVisible", [`boolean:${visible}`], callback);
|
||||
@@ -516,7 +533,7 @@ const rebootSystem = (callback = null) => {
|
||||
*/
|
||||
const processRuns = (name) => {
|
||||
try {
|
||||
return !!process.execSync(`pidof ${name}`, { encoding: "utf8" });
|
||||
return !!cpr.execSync(`pidof ${name}`, { encoding: "utf8" });
|
||||
} catch {}
|
||||
return false;
|
||||
};
|
||||
@@ -529,7 +546,7 @@ const processRuns = (name) => {
|
||||
*/
|
||||
const commandExists = (name) => {
|
||||
try {
|
||||
return !!process.execSync(`which ${name}`, { encoding: "utf8" });
|
||||
return !!cpr.execSync(`which ${name}`, { encoding: "utf8" });
|
||||
} catch {}
|
||||
return false;
|
||||
};
|
||||
@@ -543,7 +560,7 @@ const commandExists = (name) => {
|
||||
*/
|
||||
const execSyncCommand = (cmd, args = []) => {
|
||||
try {
|
||||
const output = process.execSync([cmd, ...args].join(" "), { encoding: "utf8" });
|
||||
const output = cpr.execSync([cmd, ...args].join(" "), { encoding: "utf8" });
|
||||
return output.trim().replace(/\0/g, "");
|
||||
} catch (error) {
|
||||
console.error("Execute Sync:", error.message);
|
||||
@@ -562,7 +579,7 @@ const execSyncCommand = (cmd, args = []) => {
|
||||
const execAsyncCommand = (cmd, args = [], callback = null) => {
|
||||
let errorOutput = "";
|
||||
let successOutput = "";
|
||||
let proc = process.spawn(cmd, args);
|
||||
let proc = cpr.spawn(cmd, args);
|
||||
proc.stderr.on("data", (data) => {
|
||||
if (data) {
|
||||
errorOutput += data.toString();
|
||||
@@ -606,7 +623,7 @@ const dbusCall = (path, method, values, callback = null) => {
|
||||
const dest = `${iface} ${path} ${iface}.${method} ${values.join(" ")}`;
|
||||
const args = ["--print-reply", "--type=method_call", `--dest=${dest}`];
|
||||
try {
|
||||
const output = process.execSync([cmd, ...args].join(" "), { encoding: "utf8" });
|
||||
const output = cpr.execSync([cmd, ...args].join(" "), { encoding: "utf8" });
|
||||
if (typeof callback === "function") {
|
||||
callback(output.trim().replace(/\0/g, ""), null);
|
||||
}
|
||||
@@ -628,7 +645,7 @@ const dbusCall = (path, method, values, callback = null) => {
|
||||
const dbusMonitor = (path, callback) => {
|
||||
const cmd = "dbus-monitor";
|
||||
const args = [`interface='org.freedesktop.DBus.Properties',member='PropertiesChanged',path='${path}'`];
|
||||
const proc = process.spawn(cmd, args);
|
||||
const proc = cpr.spawn(cmd, args);
|
||||
proc.stdout.on("data", (data) => {
|
||||
try {
|
||||
const signal = data.toString();
|
||||
@@ -664,6 +681,26 @@ const dbusMonitor = (path, callback) => {
|
||||
return proc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for asynchronous interval calls.
|
||||
*
|
||||
* @param {function} callback - An async callback function.
|
||||
* @param {number} ms - Sleep time in milliseconds.
|
||||
*/
|
||||
const interval = (callback, ms) => {
|
||||
const run = () => {
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
await callback();
|
||||
} catch (error) {
|
||||
console.error("Interval Callback:", error.message);
|
||||
}
|
||||
run();
|
||||
}, ms);
|
||||
};
|
||||
run();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
update,
|
||||
|
||||
+17
-12
@@ -4,23 +4,26 @@ const { app } = require("electron");
|
||||
|
||||
global.INTEGRATION = global.INTEGRATION || {
|
||||
initialized: false,
|
||||
status: "offline",
|
||||
status: null,
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the integration with the provided arguments.
|
||||
*
|
||||
* @param {Object} args - The command-line arguments to customize the initialization process.
|
||||
* @returns {boolean} Returns true if the initialization was successful.
|
||||
*/
|
||||
const init = async (args) => {
|
||||
if (!args.mqtt_url) {
|
||||
const init = async () => {
|
||||
if (!ARGS.mqtt_url) {
|
||||
return false;
|
||||
}
|
||||
const url = new URL(args.mqtt_url);
|
||||
const user = args.mqtt_user ? args.mqtt_user : null;
|
||||
const password = args.mqtt_password ? args.mqtt_password : null;
|
||||
const discovery = args.mqtt_discovery ? args.mqtt_discovery : "homeassistant";
|
||||
if (!/^mqtts?:\/\//.test(ARGS.mqtt_url)) {
|
||||
console.error("Please provide the '--mqtt-url' parameter with mqtt(s)");
|
||||
return app.quit();
|
||||
}
|
||||
const url = new URL(ARGS.mqtt_url);
|
||||
const user = ARGS.mqtt_user ? ARGS.mqtt_user : null;
|
||||
const password = ARGS.mqtt_password ? ARGS.mqtt_password : null;
|
||||
const discovery = ARGS.mqtt_discovery ? ARGS.mqtt_discovery : "homeassistant";
|
||||
|
||||
const model = hardware.getModel();
|
||||
const vendor = hardware.getVendor();
|
||||
@@ -77,11 +80,11 @@ const init = async (args) => {
|
||||
|
||||
// Integration initialized
|
||||
INTEGRATION.initialized = true;
|
||||
INTEGRATION.status = "online";
|
||||
INTEGRATION.status = "Online";
|
||||
})
|
||||
.on("error", (error) => {
|
||||
console.error("MQTT:", error);
|
||||
INTEGRATION.status = "offline";
|
||||
INTEGRATION.status = "Offline";
|
||||
});
|
||||
|
||||
// Update sensor states from notifiers
|
||||
@@ -114,7 +117,7 @@ const init = async (args) => {
|
||||
/**
|
||||
* Updates the shared integration properties.
|
||||
*/
|
||||
const update = () => {
|
||||
const update = async () => {
|
||||
if (!INTEGRATION.initialized) {
|
||||
return;
|
||||
}
|
||||
@@ -304,10 +307,12 @@ const initDisplay = (client) => {
|
||||
const status = message.toString();
|
||||
console.log("Set Display Status:", status);
|
||||
hardware.setDisplayStatus(status);
|
||||
hardware.update();
|
||||
} else if (topic === `${root}/brightness/set`) {
|
||||
const brightness = parseInt(message, 10);
|
||||
console.log("Set Display Brightness:", brightness);
|
||||
hardware.setDisplayBrightness(brightness);
|
||||
hardware.update();
|
||||
}
|
||||
})
|
||||
.subscribe(`${root}/set`)
|
||||
@@ -347,7 +352,7 @@ const initKeyboard = (client) => {
|
||||
unique_id: `${INTEGRATION.node}_keyboard`,
|
||||
command_topic: `${root}/set`,
|
||||
state_topic: `${root}/status`,
|
||||
icon: "mdi:keyboard-close",
|
||||
icon: "mdi:keyboard-close-outline",
|
||||
device: INTEGRATION.device,
|
||||
};
|
||||
client
|
||||
|
||||
+205
-71
@@ -1,11 +1,11 @@
|
||||
const path = require("path");
|
||||
const hardware = require("./hardware");
|
||||
const integration = require("./integration");
|
||||
const { app, screen, nativeTheme, BaseWindow, WebContentsView } = require("electron");
|
||||
const { app, screen, nativeTheme, ipcMain, BaseWindow, WebContentsView } = require("electron");
|
||||
|
||||
global.WEBVIEW = global.WEBVIEW || {
|
||||
initialized: false,
|
||||
status: "offline",
|
||||
status: null,
|
||||
locked: false,
|
||||
pointer: {
|
||||
position: {},
|
||||
@@ -16,36 +16,69 @@ global.WEBVIEW = global.WEBVIEW || {
|
||||
/**
|
||||
* Initializes the webview with the provided arguments.
|
||||
*
|
||||
* @param {Object} args - The command-line arguments to customize the initialization process.
|
||||
* @returns {boolean} Returns true if the initialization was successful.
|
||||
*/
|
||||
const init = async (args) => {
|
||||
if (!args.web_url) {
|
||||
const init = async () => {
|
||||
if (!ARGS.web_url) {
|
||||
console.error("Please provide the '--web-url' parameter");
|
||||
return app.quit();
|
||||
}
|
||||
const url = new URL(args.web_url);
|
||||
const theme = args.web_theme ? args.web_theme : "dark";
|
||||
const zoom = args.web_zoom ? parseFloat(args.web_zoom) : 1.25;
|
||||
if (!/^https?:\/\//.test(ARGS.web_url)) {
|
||||
console.error("Please provide the '--web-url' parameter with http(s)");
|
||||
return app.quit();
|
||||
}
|
||||
const url = new URL(ARGS.web_url);
|
||||
const theme = ["light", "dark"].includes(ARGS.web_theme) ? ARGS.web_theme : "dark";
|
||||
const zoom = !isNaN(parseFloat(ARGS.web_zoom)) ? parseFloat(ARGS.web_zoom) : 1.25;
|
||||
const widget = ARGS.web_widget ? ARGS.web_widget === "true" : true;
|
||||
const fullscreen = ARGS.app_debug !== "true";
|
||||
|
||||
// Init global layout
|
||||
WEBVIEW.viewZoom = zoom;
|
||||
WEBVIEW.viewTheme = theme;
|
||||
WEBVIEW.widgetTheme = theme;
|
||||
WEBVIEW.widgetEnabled = widget;
|
||||
nativeTheme.themeSource = WEBVIEW.viewTheme;
|
||||
|
||||
// Init global root window
|
||||
nativeTheme.themeSource = theme;
|
||||
WEBVIEW.window = new BaseWindow({
|
||||
title: `TouchKio - ${url.host}`,
|
||||
icon: path.join(__dirname, "..", "img", "icon.png"),
|
||||
});
|
||||
WEBVIEW.window.setMenuBarVisibility(false);
|
||||
WEBVIEW.window.setFullScreen(true);
|
||||
WEBVIEW.window.setFullScreen(fullscreen);
|
||||
if (!fullscreen) {
|
||||
WEBVIEW.window.maximize();
|
||||
}
|
||||
|
||||
// Init global main webview
|
||||
WEBVIEW.view = new WebContentsView();
|
||||
// Init global webview
|
||||
WEBVIEW.view = new WebContentsView({
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
},
|
||||
});
|
||||
WEBVIEW.window.contentView.addChildView(WEBVIEW.view);
|
||||
WEBVIEW.view.webContents.loadURL(url.toString());
|
||||
WEBVIEW.view.setBackgroundColor("#FFFFFFFF");
|
||||
|
||||
// Init global widget
|
||||
WEBVIEW.widget = new WebContentsView({
|
||||
transparent: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
WEBVIEW.window.contentView.addChildView(WEBVIEW.widget);
|
||||
WEBVIEW.widget.webContents.loadFile(path.join(__dirname, "widget.html"));
|
||||
WEBVIEW.widget.setBackgroundColor("#00000000");
|
||||
|
||||
// Register events
|
||||
windowEvents(WEBVIEW.window, zoom);
|
||||
touchEvents(WEBVIEW.window);
|
||||
viewEvents(WEBVIEW.view);
|
||||
windowEvents();
|
||||
widgetEvents();
|
||||
domEvents();
|
||||
appEvents();
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -53,7 +86,7 @@ const init = async (args) => {
|
||||
/**
|
||||
* Updates the shared webview properties.
|
||||
*/
|
||||
const update = () => {
|
||||
const update = async () => {
|
||||
if (!WEBVIEW.initialized) {
|
||||
return;
|
||||
}
|
||||
@@ -69,6 +102,11 @@ const update = () => {
|
||||
WEBVIEW.status = "Framed";
|
||||
}
|
||||
|
||||
// Update widget status
|
||||
if (!HARDWARE.support.keyboardVisibility) {
|
||||
WEBVIEW.widget.webContents.send("button-hide", { id: "keyboard" });
|
||||
}
|
||||
|
||||
// Update integration sensor
|
||||
console.log("Update Kiosk Status:", WEBVIEW.status);
|
||||
integration.update();
|
||||
@@ -76,67 +114,62 @@ const update = () => {
|
||||
|
||||
/**
|
||||
* Register window events and handler.
|
||||
*
|
||||
* @param {Object} window - The root window object.
|
||||
* @param {number} zoom - Zoom level used for the webview.
|
||||
*/
|
||||
const windowEvents = (window, zoom) => {
|
||||
// Handle window resize for webview bounds
|
||||
const windowEvents = () => {
|
||||
// Handle window resize events
|
||||
const resize = () => {
|
||||
const { width, height } = window.getBounds();
|
||||
WEBVIEW.view.setBounds({ x: 0, y: 0, width: width, height: height });
|
||||
WEBVIEW.view.webContents.setZoomFactor(zoom);
|
||||
const window = WEBVIEW.window.getBounds();
|
||||
const widget = { width: 60, height: 200 };
|
||||
|
||||
// Update view size
|
||||
WEBVIEW.view.setBounds({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: window.width,
|
||||
height: window.height,
|
||||
});
|
||||
WEBVIEW.view.webContents.setZoomFactor(WEBVIEW.viewZoom);
|
||||
WEBVIEW.view.webContents.focus();
|
||||
|
||||
// Update widget size
|
||||
if (WEBVIEW.widgetEnabled) {
|
||||
WEBVIEW.widget.setBounds({
|
||||
x: window.width - 15,
|
||||
y: parseInt(window.height / 2 - widget.height / 2),
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
});
|
||||
WEBVIEW.widget.webContents.send("data-theme", { theme: "hidden" });
|
||||
}
|
||||
};
|
||||
window.on("ready-to-show", resize);
|
||||
window.on("resize", resize);
|
||||
WEBVIEW.window.on("ready-to-show", resize);
|
||||
WEBVIEW.window.on("resize", resize);
|
||||
resize();
|
||||
|
||||
// Handle window status updates
|
||||
window.on("minimize", update);
|
||||
window.on("restore", update);
|
||||
window.on("maximize", update);
|
||||
window.on("unmaximize", update);
|
||||
window.on("enter-full-screen", update);
|
||||
window.on("leave-full-screen", update);
|
||||
|
||||
// Handle signal and exit events
|
||||
process.on("SIGINT", app.quit);
|
||||
app.on("before-quit", () => {
|
||||
WEBVIEW.status = "Terminated";
|
||||
integration.update();
|
||||
});
|
||||
|
||||
// Handle multiple instances
|
||||
app.on("second-instance", () => {
|
||||
if (window.isMinimized()) {
|
||||
window.restore();
|
||||
}
|
||||
window.focus();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register touch events and handler.
|
||||
*
|
||||
* @param {Object} window - The root window object.
|
||||
*/
|
||||
const touchEvents = (window) => {
|
||||
// Handle focus change for webview lock
|
||||
window.on("focus", () => {
|
||||
// Handle window focus events
|
||||
WEBVIEW.window.on("focus", () => {
|
||||
if (WEBVIEW.locked) {
|
||||
hardware.setDisplayStatus("ON");
|
||||
window.blur();
|
||||
WEBVIEW.window.blur();
|
||||
}
|
||||
WEBVIEW.locked = false;
|
||||
});
|
||||
HARDWARE.display.notifiers.push(() => {
|
||||
WEBVIEW.locked = hardware.getDisplayStatus() === "OFF";
|
||||
if (WEBVIEW.locked) {
|
||||
window.blur();
|
||||
WEBVIEW.window.blur();
|
||||
}
|
||||
});
|
||||
|
||||
// Handle touch events for activity tracking
|
||||
// Handle window status updates
|
||||
WEBVIEW.window.on("minimize", update);
|
||||
WEBVIEW.window.on("restore", update);
|
||||
WEBVIEW.window.on("maximize", update);
|
||||
WEBVIEW.window.on("unmaximize", update);
|
||||
WEBVIEW.window.on("enter-full-screen", update);
|
||||
WEBVIEW.window.on("leave-full-screen", update);
|
||||
|
||||
// Handle window touch events for activity tracking
|
||||
setInterval(() => {
|
||||
const posOld = WEBVIEW.pointer.position;
|
||||
const posNew = screen.getCursorScreenPoint();
|
||||
@@ -156,35 +189,136 @@ const touchEvents = (window) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Register webview events and handler.
|
||||
*
|
||||
* @param {Object} view - The main webview object.
|
||||
* Register widget events and handler.
|
||||
*/
|
||||
const viewEvents = (view) => {
|
||||
const widgetEvents = () => {
|
||||
if (!WEBVIEW.widgetEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle widget focus events
|
||||
WEBVIEW.widget.webContents.on("focus", () => {
|
||||
const window = WEBVIEW.window.getBounds();
|
||||
const widget = WEBVIEW.widget.getBounds();
|
||||
|
||||
// Show widget
|
||||
WEBVIEW.widget.setBounds({
|
||||
x: window.width - 60,
|
||||
y: widget.y,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
});
|
||||
WEBVIEW.widget.webContents.send("data-theme", { theme: WEBVIEW.widgetTheme });
|
||||
});
|
||||
|
||||
// Handle widget blur events
|
||||
WEBVIEW.widget.webContents.on("blur", () => {
|
||||
const window = WEBVIEW.window.getBounds();
|
||||
const widget = WEBVIEW.widget.getBounds();
|
||||
|
||||
// Hide widget
|
||||
WEBVIEW.widget.setBounds({
|
||||
x: window.width - 15,
|
||||
y: widget.y,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
});
|
||||
WEBVIEW.widget.webContents.send("data-theme", { theme: "hidden" });
|
||||
});
|
||||
|
||||
// Handle widget button click events
|
||||
ipcMain.on("button-click", (e, button) => {
|
||||
switch (button.id) {
|
||||
case "refresh":
|
||||
WEBVIEW.view.webContents.reloadIgnoringCache();
|
||||
break;
|
||||
case "fullscreen":
|
||||
if (WEBVIEW.window.isFullScreen()) {
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.unmaximize();
|
||||
WEBVIEW.window.setFullScreen(false);
|
||||
} else {
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.unmaximize();
|
||||
WEBVIEW.window.setFullScreen(true);
|
||||
}
|
||||
break;
|
||||
case "minimize":
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.setFullScreen(false);
|
||||
WEBVIEW.window.minimize();
|
||||
break;
|
||||
case "keyboard":
|
||||
const toggle = hardware.getKeyboardVisibility() === "ON" ? "OFF" : "ON";
|
||||
hardware.setKeyboardVisibility(toggle);
|
||||
switch (toggle) {
|
||||
case "OFF":
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.unmaximize();
|
||||
WEBVIEW.window.setFullScreen(true);
|
||||
break;
|
||||
case "ON":
|
||||
WEBVIEW.window.restore();
|
||||
WEBVIEW.window.setFullScreen(false);
|
||||
WEBVIEW.window.maximize();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register dom events and handler.
|
||||
*/
|
||||
const domEvents = () => {
|
||||
// Enable webview touch emulation
|
||||
view.webContents.debugger.attach("1.1");
|
||||
view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", {
|
||||
WEBVIEW.view.webContents.debugger.attach("1.1");
|
||||
WEBVIEW.view.webContents.debugger.sendCommand("Emulation.setEmitTouchEventsForMouse", {
|
||||
configuration: "mobile",
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
// Disable webview hyperlinks
|
||||
view.webContents.setWindowOpenHandler(() => {
|
||||
WEBVIEW.view.webContents.setWindowOpenHandler(() => {
|
||||
return { action: "deny" };
|
||||
});
|
||||
|
||||
// Remove webview scrollbar
|
||||
view.webContents.on("dom-ready", () => {
|
||||
view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
|
||||
WEBVIEW.view.webContents.on("dom-ready", () => {
|
||||
WEBVIEW.view.webContents.insertCSS("::-webkit-scrollbar { display: none; }");
|
||||
});
|
||||
|
||||
// Webview fully loaded
|
||||
view.webContents.on("did-finish-load", () => {
|
||||
WEBVIEW.view.webContents.on("did-finish-load", () => {
|
||||
if (ARGS.app_debug === "true") {
|
||||
WEBVIEW.view.webContents.openDevTools();
|
||||
}
|
||||
WEBVIEW.initialized = true;
|
||||
update();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Register app events and handler.
|
||||
*/
|
||||
const appEvents = () => {
|
||||
// Handle signal and exit events
|
||||
process.on("SIGINT", app.quit);
|
||||
app.on("before-quit", () => {
|
||||
WEBVIEW.status = "Terminated";
|
||||
integration.update();
|
||||
});
|
||||
|
||||
// Handle multiple instances
|
||||
app.on("second-instance", () => {
|
||||
if (WEBVIEW.window.isMinimized()) {
|
||||
WEBVIEW.window.restore();
|
||||
}
|
||||
WEBVIEW.window.focus();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
update,
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
[data-theme="dark"] {
|
||||
--primary-color: rgba(225, 225, 225, 1);
|
||||
--border-color: rgba(225, 225, 225, 0.15);
|
||||
--background-color: rgba(28, 28, 28, 1);
|
||||
}
|
||||
[data-theme="light"] {
|
||||
--primary-color: rgba(33, 33, 33, 1);
|
||||
--border-color: rgba(33, 33, 33, 0.15);
|
||||
--background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
[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: pointer;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100vw;
|
||||
padding: 10px;
|
||||
row-gap: 10px;
|
||||
list-style-type: none;
|
||||
border-top-left-radius: 15px;
|
||||
border-bottom-left-radius: 15px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-left: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
background: var(--background-color);
|
||||
}
|
||||
button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
svg {
|
||||
pointer-events: none;
|
||||
fill: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<menu>
|
||||
<li>
|
||||
<button id="refresh">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<title>web-refresh</title>
|
||||
<path
|
||||
d="M13.82 14H9.66C9.56 13.34 9.5 12.68 9.5 12S9.56 10.65 9.66 10H14.34C14.43 10.65 14.5 11.32 14.5 12C14.5 12.5 14.46 13 14.4 13.46C15 12.96 15.72 12.57 16.5 12.32C16.5 12.21 16.5 12.11 16.5 12C16.5 11.32 16.44 10.66 16.36 10H19.74C19.9 10.64 20 11.31 20 12C20 12.06 20 12.12 20 12.18C20.7 12.35 21.35 12.63 21.95 13C22 12.68 22 12.34 22 12C22 6.5 17.5 2 12 2C6.47 2 2 6.5 2 12C2 17.5 6.5 22 12 22C12.34 22 12.68 22 13 21.95C12.59 21.29 12.29 20.55 12.13 19.75C12.09 19.82 12.05 19.89 12 19.96C11.17 18.76 10.5 17.43 10.09 16H12.5C12.81 15.25 13.26 14.58 13.82 14M18.92 8H15.97C15.65 6.75 15.19 5.55 14.59 4.44C16.43 5.07 17.96 6.34 18.92 8M12 4.03C12.83 5.23 13.5 6.57 13.91 8H10.09C10.5 6.57 11.17 5.23 12 4.03M4.26 14C4.1 13.36 4 12.69 4 12S4.1 10.64 4.26 10H7.64C7.56 10.66 7.5 11.32 7.5 12S7.56 13.34 7.64 14H4.26M5.08 16H8C8.35 17.25 8.8 18.45 9.4 19.56C7.57 18.93 6.03 17.65 5.08 16M8 8H5.08C6.03 6.34 7.57 5.06 9.4 4.44C8.8 5.55 8.35 6.75 8 8M20.83 15.67L22 14.5V18.5H18L19.77 16.73C19.32 16.28 18.69 16 18 16C16.62 16 15.5 17.12 15.5 18.5S16.62 21 18 21C18.82 21 19.54 20.61 20 20H21.71C21.12 21.47 19.68 22.5 18 22.5C15.79 22.5 14 20.71 14 18.5S15.79 14.5 18 14.5C19.11 14.5 20.11 14.95 20.83 15.67Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="fullscreen">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<title>overscan</title>
|
||||
<path
|
||||
d="M12 5.5L10 8H14L12 5.5M18 10V14L20.5 12L18 10M6 10L3.5 12L6 14V10M14 16H10L12 18.5L14 16M21 3H3C1.9 3 1 3.9 1 5V19C1 20.1 1.9 21 3 21H21C22.1 21 23 20.1 23 19V5C23 3.9 22.1 3 21 3M21 19H3V5H21V19Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="minimize">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<title>image-size-select-small</title>
|
||||
<path
|
||||
d="M23,15H21V17H23V15M23,11H21V13H23V11M23,19H21V21C22,21 23,20 23,19M15,3H13V5H15V3M23,7H21V9H23V7M21,3V5H23C23,4 22,3 21,3M3,21H11V15H1V19A2,2 0 0,0 3,21M3,7H1V9H3V7M15,19H13V21H15V19M19,3H17V5H19V3M19,19H17V21H19V19M3,3C2,3 1,4 1,5H3V3M3,11H1V13H3V11M11,3H9V5H11V3M7,3H5V5H7V3Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button id="keyboard">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<title>keyboard-close-outline</title>
|
||||
<path
|
||||
d="M12 23L16 19H8M4 3C2.9 3 2 3.9 2 5V15C2 16.1 2.9 17 4 17H20C21.1 17 22 16.1 22 15V5C22 3.9 21.1 3 20 3H4M4 5H20V15H4V5M5 6V8H7V6H5M8 6V8H10V6H8M11 6V8H13V6H11M14 6V8H16V6H14M17 6V8H19V6H17M5 9V11H7V9H5M8 9V11H10V9H8M11 9V11H13V9H11M14 9V11H16V9H14M17 9V11H19V9H17M8 12V14H16V12H8Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</li>
|
||||
</menu>
|
||||
<script>
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
// Register button click send message
|
||||
document.querySelectorAll("button").forEach((button) => {
|
||||
button.addEventListener("click", (e) => {
|
||||
ipcRenderer.send("button-click", { id: e.target.id });
|
||||
});
|
||||
});
|
||||
|
||||
// Register button hide receive message
|
||||
ipcRenderer.on("button-hide", (e, button) => {
|
||||
document.getElementById(button.id).parentNode.hidden = true;
|
||||
});
|
||||
|
||||
// Register data theme receive message
|
||||
ipcRenderer.on("data-theme", (e, data) => {
|
||||
document.documentElement.setAttribute("data-theme", data.theme);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user