Files
InputRemapper/tests/lib/cleanup.py
Blomios 0ab77d2a64 chore(repo): baseline du fork input-remapper (upstream v2.2.1)
Le dépôt ne contenait que README.md ; ajout de l'intégralité du code
fonctionnel du fork comme socle stable de la branche de release.
L'état runtime d'orchestration (.ideai/) est exclu du suivi.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 00:47:12 +02:00

173 lines
5.1 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# input-remapper - GUI for device specific keyboard mappings
# Copyright (C) 2025 sezanzeb <b8x45ygc9@mozmail.com>
#
# This file is part of input-remapper.
#
# input-remapper is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# input-remapper is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with input-remapper. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations
import asyncio
import copy
import os
import shutil
import time
from pickle import UnpicklingError
import psutil
from tests.lib.constants import EVENT_READ_TIMEOUT
from tests.lib.fixtures import fixtures
from tests.lib.logger import logger
from tests.lib.patches import uinputs
from tests.lib.pipes import (
uinput_write_history_pipe,
uinput_write_history,
pending_events,
setup_pipe,
)
from tests.lib.tmp import tmp
# TODO on it. You don't need a framework for this by the way:
# don't import anything from input_remapper gloablly here, because some files execute
# code when imported, which can screw up patches. I wish we had a dependency injection
# framework that patches together the dependencies during runtime...
environ_copy = copy.deepcopy(os.environ)
def join_children():
"""Wait for child processes to exit. Stop them if it takes too long."""
this = psutil.Process(os.getpid())
i = 0
time.sleep(EVENT_READ_TIMEOUT)
children = this.children(recursive=True)
while len([c for c in children if c.status() != "zombie"]) > 0:
for child in children:
if i > 10:
child.kill()
logger.info("Killed pid %s because it didn't finish in time", child.pid)
children = this.children(recursive=True)
time.sleep(EVENT_READ_TIMEOUT)
i += 1
def clear_write_history():
"""Empty the history in preparation for the next test."""
while len(uinput_write_history) > 0:
uinput_write_history.pop()
while uinput_write_history_pipe[0].poll():
uinput_write_history_pipe[0].recv()
def quick_cleanup(log=True):
"""Reset the applications state."""
# TODO no:
# Reminder: before patches are applied in test.py, no inputremapper module
# may be imported. So tests.lib imports them just-in-time in functions instead.
from inputremapper.injection.macros.macro import macro_variables
from inputremapper.configs.keyboard_layout import keyboard_layout
from inputremapper.gui.utils import debounce_manager
if log:
logger.info("Quick cleanup...")
debounce_manager.stop_all()
for device in list(pending_events.keys()):
try:
while pending_events[device][1].poll():
pending_events[device][1].recv()
except (UnpicklingError, EOFError):
pass
# setup new pipes for the next test
pending_events[device][1].close()
pending_events[device][0].close()
del pending_events[device]
setup_pipe(device)
try:
if asyncio.get_event_loop().is_running():
for task in asyncio.all_tasks():
task.cancel()
except RuntimeError:
# happens when the event loop disappears for magical reasons
# create a fresh event loop
asyncio.set_event_loop(asyncio.new_event_loop())
if macro_variables.process is not None and not macro_variables.process.is_alive():
# nothing should stop the process during runtime, if it has been started by
# the injector once
raise AssertionError("the SharedDict manager is not running anymore")
if macro_variables.process is not None:
macro_variables._stop()
join_children()
macro_variables.start()
if os.path.exists(tmp):
shutil.rmtree(tmp)
keyboard_layout.populate()
clear_write_history()
for name in list(uinputs.keys()):
del uinputs[name]
# for device in list(active_macros.keys()):
# del active_macros[device]
# for device in list(unreleased.keys()):
# del unreleased[device]
fixtures.reset()
os.environ.update(environ_copy)
for device in list(os.environ.keys()):
if device not in environ_copy:
del os.environ[device]
for _, pipe in pending_events.values():
assert not pipe.poll()
assert macro_variables.is_alive(1)
if log:
logger.info("Quick cleanup done")
def cleanup():
"""Reset the applications state.
Using this is slower, usually quick_cleanup() is sufficient.
"""
from inputremapper.groups import groups
logger.info("Cleanup...")
os.system("pkill -f input-remapper-service")
os.system("pkill -f input-remapper-control")
time.sleep(0.05)
quick_cleanup(log=False)
groups.refresh()
logger.info("Cleanup done")