From 17c1aae84ba87dd038db3a5b554318f1c1d8a546 Mon Sep 17 00:00:00 2001 From: Michel Fedde Date: Sat, 10 May 2025 18:44:27 +0200 Subject: [PATCH] Adds hyprland support for evremap orchestrator --- .config/evremap/orchestrator | 200 ++++++++++++++++++++---- .config/hypr/config/settings/Input.conf | 12 +- 2 files changed, 171 insertions(+), 41 deletions(-) diff --git a/.config/evremap/orchestrator b/.config/evremap/orchestrator index d7b6502..5088688 100755 --- a/.config/evremap/orchestrator +++ b/.config/evremap/orchestrator @@ -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 + * @type Set */ #devices @@ -243,12 +352,16 @@ class Orchestrator { #notifications /** @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,21 +551,40 @@ ${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; } - + resolve(stdout); }) }) + 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); diff --git a/.config/hypr/config/settings/Input.conf b/.config/hypr/config/settings/Input.conf index 7e7facd..5b1213c 100644 --- a/.config/hypr/config/settings/Input.conf +++ b/.config/hypr/config/settings/Input.conf @@ -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