Adds hyprland support for evremap orchestrator
This commit is contained in:
parent
0f36495ddd
commit
17c1aae84b
2 changed files with 171 additions and 41 deletions
|
|
@ -200,18 +200,127 @@ class Logger {
|
|||
}
|
||||
}
|
||||
|
||||
class HyprlandSupport {
|
||||
|
||||
static HYPR_INPUT_CONFIG_FILE = path.join(process.env.HOME, '.config/hypr/config/settings/Input.conf')
|
||||
static TARGET_CONFIG_FILE = path.join(process.env.HOME, '.cache/evremap_hyprland_config.conf')
|
||||
|
||||
#lastFile = '';
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isValid() {
|
||||
return process.env.DESKTOP_SESSION === 'hyprland';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FileContainer[]} activeFiles
|
||||
*/
|
||||
update(files) {
|
||||
const configurations = this.#getConfigurations();
|
||||
const activeFiles = files.filter((file) => file.state === 'running');
|
||||
|
||||
const activeConfigurations =
|
||||
configurations.filter((config) =>
|
||||
activeFiles.some((file) => {
|
||||
const normalisedDeviceName = this.#getNormalizedDeviceName(file.device.name)
|
||||
return config.name === normalisedDeviceName
|
||||
})
|
||||
)
|
||||
|
||||
this.#createFile(activeFiles, activeConfigurations)
|
||||
}
|
||||
|
||||
#getConfigurations() {
|
||||
const config = fs.readFileSync(HyprlandSupport.HYPR_INPUT_CONFIG_FILE, 'utf-8');
|
||||
const lines = config.split('\n').filter(line => line);
|
||||
|
||||
const configurations = [];
|
||||
let currentConfiguration;
|
||||
let inDevice = false;
|
||||
for (const line of lines) {
|
||||
if (!inDevice) {
|
||||
if (line === 'device {') {
|
||||
inDevice = true;
|
||||
|
||||
currentConfiguration = {};
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line === '}') {
|
||||
inDevice = false;
|
||||
|
||||
configurations.push(currentConfiguration);
|
||||
currentConfiguration = {};
|
||||
continue;
|
||||
}
|
||||
|
||||
const [ name, value ] = line.split('=');
|
||||
currentConfiguration[name.trim()] = value.trim()
|
||||
}
|
||||
|
||||
return configurations;
|
||||
}
|
||||
|
||||
#createFile(files, configurations) {
|
||||
let configFileContent = "";
|
||||
|
||||
for (const file of files) {
|
||||
const configuration = configurations.find(
|
||||
(config) => config.name === this.#getNormalizedDeviceName(file.device.name)
|
||||
)
|
||||
if (!configuration) {
|
||||
continue
|
||||
}
|
||||
|
||||
configuration.name = `evremap-virtual-input-for-${file.device.path}`;
|
||||
|
||||
const entry = Object.entries(configuration)
|
||||
.map(([name, value]) => ` ${name} = ${value}`)
|
||||
.join('\n');
|
||||
|
||||
configFileContent += `device {\n${entry}\n}\n`;
|
||||
}
|
||||
|
||||
if (this.#lastFile === configFileContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#lastFile = configFileContent
|
||||
|
||||
fs.writeFileSync(HyprlandSupport.TARGET_CONFIG_FILE, configFileContent);
|
||||
console.log("Writing new config")
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} deviceName
|
||||
* @returns {string}
|
||||
*/
|
||||
#getNormalizedDeviceName(deviceName) {
|
||||
return deviceName.replaceAll(' ', '-').toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {"waiting"|"running"|"failed"|"destroy"|"invalid"} FileContainerState
|
||||
*
|
||||
* @typedef {{
|
||||
* filepath: Path,
|
||||
* device: DeviceName,
|
||||
* device: Device,
|
||||
* process?: ChildProcess,
|
||||
* state: FileContainerState
|
||||
* }} FileContainer
|
||||
*
|
||||
* @typedef {string} Path
|
||||
* @typedef {string} DeviceName
|
||||
*
|
||||
* @typedef {{
|
||||
* name: DeviceName
|
||||
* path: string,
|
||||
* physical: string,
|
||||
* }} Device
|
||||
**/
|
||||
|
||||
class Orchestrator {
|
||||
|
|
@ -230,7 +339,7 @@ class Orchestrator {
|
|||
*/
|
||||
#fileMap
|
||||
/**
|
||||
* @type Set<DeviceName>
|
||||
* @type Set<Device>
|
||||
*/
|
||||
#devices
|
||||
|
||||
|
|
@ -244,11 +353,15 @@ class Orchestrator {
|
|||
/** @type {Logger} */
|
||||
#logger
|
||||
|
||||
/** @type {HyprlandSupport} */
|
||||
#support;
|
||||
|
||||
/** @type {'waiting'|'running'|'stopping'|'stopped'|'failed'} */
|
||||
#state
|
||||
constructor(
|
||||
path,
|
||||
logger
|
||||
logger,
|
||||
support
|
||||
) {
|
||||
this.#path = path
|
||||
this.#fileMap = new Map();
|
||||
|
|
@ -256,6 +369,7 @@ class Orchestrator {
|
|||
this.#notifications = new NotificationHandler();
|
||||
this.#logger = logger;
|
||||
this.#state = 'waiting';
|
||||
this.#support = support;
|
||||
}
|
||||
|
||||
async run() {
|
||||
|
|
@ -272,14 +386,15 @@ class Orchestrator {
|
|||
|
||||
this.#state = 'running';
|
||||
do {
|
||||
const prepareSteps = [
|
||||
this.#updateFileMap(),
|
||||
this.#updateDevices()
|
||||
];
|
||||
await Promise.all(prepareSteps);
|
||||
await this.#updateDevices();
|
||||
await this.#updateFileMap();
|
||||
|
||||
this.#actFiles()
|
||||
|
||||
if (this.#support.isValid) {
|
||||
this.#support.update(this.#fileMap.values().toArray());
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
} while(this.#state === 'running');
|
||||
this.#state = 'stopped';
|
||||
|
|
@ -347,18 +462,23 @@ class Orchestrator {
|
|||
*/
|
||||
#createFileContainer(path) {
|
||||
const grepResult = child_process.execSync(`grep device_name "${path}"`).toString();
|
||||
const device = grepResult.split('=')[1].replaceAll('"', '').trim();
|
||||
const deviceName = grepResult.split('=')[1].replaceAll('"', '').trim();
|
||||
|
||||
this.#logger.log(
|
||||
`Create file watcher for ${device} with '${path}'`,
|
||||
`Create file watcher for ${deviceName} with '${path}'`,
|
||||
"ORCHESTRATOR",
|
||||
"DEBUG"
|
||||
)
|
||||
|
||||
/**
|
||||
* @type {Device}
|
||||
*/
|
||||
const selectedDevice = this.#devices.values().find((device) => device.name === deviceName)
|
||||
|
||||
return {
|
||||
filepath: path,
|
||||
state: "waiting",
|
||||
device: device
|
||||
device: selectedDevice
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -372,11 +492,11 @@ class Orchestrator {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!deviceMap.has(value.device)) {
|
||||
deviceMap.set(value.device, [])
|
||||
if (!deviceMap.has(value.device.name)) {
|
||||
deviceMap.set(value.device.name, [])
|
||||
}
|
||||
|
||||
deviceMap.get(value.device).push(value);
|
||||
deviceMap.get(value.device.name).push(value);
|
||||
}
|
||||
|
||||
const duplicates = deviceMap.values().filter(devices => devices.some(device => device.state !== 'invalid') && devices.length > 1);
|
||||
|
|
@ -385,7 +505,7 @@ class Orchestrator {
|
|||
this.#notifications.sendNotification(
|
||||
{
|
||||
summary: "Duplicate remap files for device",
|
||||
message: `The following device ${container.device} has multiple remap files. This is not possible. Please make sure only one is avaliable.
|
||||
message: `The following device ${container.device.name} has multiple remap files. This is not possible. Please make sure only one is avaliable.
|
||||
Marking all files as invalid and unloading currently loaded files...
|
||||
|
||||
Files:
|
||||
|
|
@ -401,7 +521,7 @@ ${duplicate.map((file) => `- ${file.filepath}`).join('\n')}`,
|
|||
});
|
||||
})
|
||||
this.#logger.log(
|
||||
`Marking device ${container.device} invalid due to multiple files.
|
||||
`Marking device ${container.device.name} invalid due to multiple files.
|
||||
Files:
|
||||
${duplicate.map((file) => `- ${file.filepath}`).join('\n')}\`
|
||||
`,
|
||||
|
|
@ -431,7 +551,7 @@ ${duplicate.map((file) => `- ${file.filepath}`).join('\n')}\`
|
|||
* @var {string}
|
||||
*/
|
||||
const evremapResult = await new Promise((resolve, reject) => {
|
||||
child_process.exec('evremap list-devices | grep Name:', (error, stdout, stderr) => {
|
||||
child_process.exec('evremap list-devices', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
|
|
@ -441,11 +561,30 @@ ${duplicate.map((file) => `- ${file.filepath}`).join('\n')}\`
|
|||
})
|
||||
})
|
||||
|
||||
const lines = evremapResult.split('\n').filter(line => line);
|
||||
|
||||
/**
|
||||
* @type {DeviceName[]}
|
||||
* @type {Device[]}
|
||||
*/
|
||||
const devices = evremapResult.split('\n').map((result) => result.replace('Name: ', ''))
|
||||
const devices = [];
|
||||
for (let i = 0; i < lines.length; i += 3) {
|
||||
const nameLine = lines[i];
|
||||
const pathLine = lines[i + 1];
|
||||
const physLine = lines[i + 2];
|
||||
|
||||
/**
|
||||
* @type {Device}
|
||||
*/
|
||||
const device = {
|
||||
name: nameLine.split(':')[1]?.trim() ?? '',
|
||||
path: pathLine.split(':')[1]?.trim() ?? '',
|
||||
physical: physLine.split(':')[1]?.trim() ?? ''
|
||||
}
|
||||
|
||||
devices.push(device);
|
||||
}
|
||||
this.#devices = new Set(devices);
|
||||
|
||||
}
|
||||
|
||||
#actFiles() {
|
||||
|
|
@ -495,7 +634,7 @@ s
|
|||
if (data.includes("Going into read loop")) {
|
||||
message.replace({
|
||||
summary: "Device ready",
|
||||
message: Orchestrator.NOTIFICATION_READY.replace("{device}", file.device),
|
||||
message: Orchestrator.NOTIFICATION_READY.replace("{device}", file.device.name),
|
||||
expire: 5_000,
|
||||
urgency: 'normal'
|
||||
})
|
||||
|
|
@ -505,7 +644,7 @@ s
|
|||
if (!dataMessage) {
|
||||
this.#logger.log(
|
||||
dataMetadata,
|
||||
`EVREMAP[${file.device}]`,
|
||||
`EVREMAP[${file.device.name}]`,
|
||||
'WARN',
|
||||
)
|
||||
return;
|
||||
|
|
@ -515,7 +654,7 @@ s
|
|||
|
||||
this.#logger.log(
|
||||
dataMessage.trim(),
|
||||
origin.toUpperCase().replace('EVREMAP', `EVREMAP[${file.device}]`),
|
||||
origin.toUpperCase().replace('EVREMAP', `EVREMAP[${file.device.name}]`),
|
||||
type,
|
||||
new Date(Date.parse(time))
|
||||
)
|
||||
|
|
@ -524,8 +663,8 @@ s
|
|||
const unexpected = code === null || code === 0;
|
||||
|
||||
this.#logger.log(
|
||||
`Device '${file.device}' ${unexpected ? "unexpectedly" : ''} left with code '${code}'.`,
|
||||
`EVREMAP[${file.device}]`,
|
||||
`Device '${file.device.name}' ${unexpected ? "unexpectedly" : ''} left with code '${code}'.`,
|
||||
`EVREMAP[${file.device.name}]`,
|
||||
unexpected ? 'WARN' : 'INFO'
|
||||
)
|
||||
|
||||
|
|
@ -535,7 +674,7 @@ s
|
|||
|
||||
message.replace({
|
||||
summary: "Device left unexpected...",
|
||||
message: Orchestrator.NOTIFICATION_CONNECTION_LOST.replace('{device}', file.device)
|
||||
message: Orchestrator.NOTIFICATION_CONNECTION_LOST.replace('{device}', file.device.name)
|
||||
})
|
||||
|
||||
this.#fileMap.set(file.filepath, {
|
||||
|
|
@ -571,15 +710,15 @@ s
|
|||
default:
|
||||
case "GENERIC":
|
||||
notificationMessage = Orchestrator.NOTIFICATION_GENERIC_PROCESS_STOP.replace('{device}', file.device)
|
||||
logMessage = `Removing process for device '${file.device}' from file '${file.filepath}', due to unknown reason`
|
||||
logMessage = `Removing process for device '${file.device.name}' from file '${file.filepath}', due to unknown reason`
|
||||
break;
|
||||
case "REMOVED_FILE":
|
||||
notificationMessage = Orchestrator.NOTIFICATION_MISSING_FILE_PROCESS_STOP.replace('{device}', file.device)
|
||||
logMessage = `Removing process for device '${file.device}' from file '${file.filepath}', due to a removed file`
|
||||
logMessage = `Removing process for device '${file.device.name}' from file '${file.filepath}', due to a removed file`
|
||||
break;
|
||||
case "INVALID_FILE":
|
||||
notificationMessage = Orchestrator.NOTIFICATION_INVALID_FILE_PROCESS_STOP.replace('{device}', file.device)
|
||||
logMessage = `Removing process for device '${file.device}' from file '${file.filepath}', due to a invalid file`
|
||||
logMessage = `Removing process for device '${file.device.name}' from file '${file.filepath}', due to a invalid file`
|
||||
break;
|
||||
|
||||
}
|
||||
|
|
@ -627,7 +766,8 @@ function prepareProcess(orchestrator) {
|
|||
const evremapPath = path.resolve(process.env.HOME, '.config/evremap');
|
||||
|
||||
const logger = new Logger("CHATTY");
|
||||
const orchestrator = new Orchestrator(evremapPath, logger);
|
||||
const support = new HyprlandSupport();
|
||||
const orchestrator = new Orchestrator(evremapPath, logger, support);
|
||||
|
||||
prepareProcess(orchestrator);
|
||||
|
||||
|
|
|
|||
|
|
@ -36,14 +36,4 @@ device {
|
|||
kb_variant = altgr-intl
|
||||
}
|
||||
|
||||
device {
|
||||
name = evremap-virtual-input-for-/dev/input/event17
|
||||
kb_layout = us
|
||||
kb_variant = altgr-intl
|
||||
}
|
||||
|
||||
device {
|
||||
name = evremap-virtual-input-for-/dev/input/event4
|
||||
kb_layout = us
|
||||
kb_variant = altgr-intl
|
||||
}
|
||||
source = ~/.cache/evremap_hyprland_config.conf
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue