Initial commit
This commit is contained in:
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual Environment
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 BongoCatXP Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
134
README.md
Normal file
134
README.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# TapTapLoot Forwarder
|
||||||
|
|
||||||
|
Forward keyboard input to [TapTapLoot](https://store.steampowered.com/app/your_app_id) running in Xwayland on Linux, so it can run in the background while you use other apps — similar to how BongoCat works.
|
||||||
|
|
||||||
|
> **Based on [BongoCatXP](https://github.com/ITJesse/BongoCatXP) by ITJesse** — adapted for TapTapLoot with Xwayland support and dynamic xauth handling.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
TapTapLoot is launched in a rootful Xwayland instance (via a Steam launch script), which lets it run independently of your Wayland compositor. This tool uses `evdev` to read your keyboard at the device level and forwards keypresses to the TapTapLoot window via `xdotool`, without requiring the window to have focus.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux (Wayland or X11)
|
||||||
|
- Python 3.7+
|
||||||
|
- `xdotool`
|
||||||
|
- `xdotool` must be installed
|
||||||
|
- Access to `/dev/input/` devices (via `input` group)
|
||||||
|
- TapTapLoot running via the Xwayland launch script (see below)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### 1. Install system dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Arch Linux / Bazzite / SteamOS
|
||||||
|
sudo pacman -S xdotool python-pipx
|
||||||
|
# or on immutable distros (Bazzite, SteamOS):
|
||||||
|
ujust setup-decky # if needed, then use distrobox or toolbox for pipx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Install this tool
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From this repo
|
||||||
|
pipx install git+https://gitea.example.com/yourname/TapTapLootForwarder.git
|
||||||
|
|
||||||
|
# Or from a local clone
|
||||||
|
git clone https://gitea.example.com/yourname/TapTapLootForwarder.git
|
||||||
|
cd TapTapLootForwarder
|
||||||
|
pipx install .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Add yourself to the input group
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG input $USER
|
||||||
|
# Log out and back in for this to take effect
|
||||||
|
```
|
||||||
|
|
||||||
|
## Steam Launch Script
|
||||||
|
|
||||||
|
Save the following as `~/.local/bin/taptaploot-forward.sh` and make it executable (`chmod +x`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Launches TapTapLoot in rootful Xwayland
|
||||||
|
# Steam launch option: ~/.local/bin/taptaploot-forward.sh %command%
|
||||||
|
|
||||||
|
for i in $(seq 10 30); do
|
||||||
|
if [ ! -e "/tmp/.X11-unix/X$i" ]; then
|
||||||
|
GAME_DISPLAY=":$i"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
GAME_XAUTH=$(mktemp /tmp/taptaploot-xauth.XXXXXX)
|
||||||
|
COOKIE=$(mcookie)
|
||||||
|
xauth -f "$GAME_XAUTH" add "$GAME_DISPLAY" . "$COOKIE"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
kill "$XWAYLAND_PID" 2>/dev/null
|
||||||
|
wait "$XWAYLAND_PID" 2>/dev/null
|
||||||
|
rm -f "$GAME_XAUTH"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
XAUTHORITY="$GAME_XAUTH" Xwayland "$GAME_DISPLAY" -geometry 1920x1080 -noreset -auth "$GAME_XAUTH" &
|
||||||
|
XWAYLAND_PID=$!
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
DISPLAY="$GAME_DISPLAY" XAUTHORITY="$GAME_XAUTH" "$@" &
|
||||||
|
GAME_PID=$!
|
||||||
|
|
||||||
|
wait "$GAME_PID"
|
||||||
|
```
|
||||||
|
|
||||||
|
Set your Steam launch option to:
|
||||||
|
```
|
||||||
|
~/.local/bin/taptaploot-forward.sh %command%
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Launch TapTapLoot via Steam (using the launch script above)
|
||||||
|
2. In a terminal, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
taptaploot-forwarder
|
||||||
|
```
|
||||||
|
|
||||||
|
Keypresses will be forwarded to the game without it needing focus. Press `Ctrl+C` to stop.
|
||||||
|
|
||||||
|
## Bazzite / Immutable Distro Installation
|
||||||
|
|
||||||
|
On Bazzite, Nobara, or SteamOS (which use immutable root filesystems), the recommended approach is to use a **Distrobox container**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create an Arch-based container
|
||||||
|
distrobox create --name taptaploot --image archlinux:latest
|
||||||
|
distrobox enter taptaploot
|
||||||
|
|
||||||
|
# Inside the container:
|
||||||
|
sudo pacman -S xdotool python-pipx
|
||||||
|
sudo usermod -aG input $USER
|
||||||
|
pipx install git+https://gitea.example.com/yourname/TapTapLootForwarder.git
|
||||||
|
|
||||||
|
# Export the command to your host
|
||||||
|
distrobox-export --bin ~/.local/bin/taptaploot-forwarder
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `taptaploot-forwarder` from your host terminal as normal.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**Permission denied on `/dev/input/`** — make sure you've added yourself to the `input` group and logged out/in. Temporarily use `sudo taptaploot-forwarder`.
|
||||||
|
|
||||||
|
**Window not found** — ensure TapTapLoot is running via the Xwayland launch script. The forwarder checks every 10 seconds.
|
||||||
|
|
||||||
|
**No keyboard devices found** — permission issue, see above.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- [BongoCatXP](https://github.com/ITJesse/BongoCatXP) by [ITJesse](https://github.com/ITJesse) — original implementation this is based on
|
||||||
|
- MIT License
|
||||||
302
main.py
Normal file
302
main.py
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import evdev
|
||||||
|
from evdev import InputDevice, ecodes
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
import queue
|
||||||
|
import time
|
||||||
|
import shutil
|
||||||
|
import argparse
|
||||||
|
import glob
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
|
|
||||||
|
BongoWindow = None
|
||||||
|
stop_event = threading.Event()
|
||||||
|
keyboard_stop_event = threading.Event()
|
||||||
|
key_queue = queue.Queue()
|
||||||
|
keyboard_threads = []
|
||||||
|
keyboard_devices = []
|
||||||
|
|
||||||
|
TARGET_WINDOW_NAME = 'TapTapLoot'
|
||||||
|
TARGET_DISPLAY = ':10'
|
||||||
|
|
||||||
|
def get_xauth_path():
|
||||||
|
"""Dynamically find the current taptaploot xauth file"""
|
||||||
|
matches = glob.glob('/tmp/taptaploot-xauth.*')
|
||||||
|
if matches:
|
||||||
|
return matches[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_xenv():
|
||||||
|
"""Get environment dict for xdotool calls targeting the game display"""
|
||||||
|
xauth = get_xauth_path()
|
||||||
|
env = {**os.environ, 'DISPLAY': TARGET_DISPLAY}
|
||||||
|
if xauth:
|
||||||
|
env['XAUTHORITY'] = xauth
|
||||||
|
return env
|
||||||
|
|
||||||
|
def get_xdotool_keyname(keycode):
|
||||||
|
key_name = ecodes.KEY[keycode]
|
||||||
|
if key_name.startswith('KEY_'):
|
||||||
|
key_name = key_name[4:]
|
||||||
|
elif key_name.startswith('BTN_'):
|
||||||
|
key_name = key_name[4:]
|
||||||
|
key_name = key_name.lower()
|
||||||
|
special_keys = {
|
||||||
|
'leftctrl': 'ctrl',
|
||||||
|
'rightctrl': 'ctrl',
|
||||||
|
'leftshift': 'shift',
|
||||||
|
'rightshift': 'shift',
|
||||||
|
'leftalt': 'alt',
|
||||||
|
'rightalt': 'alt',
|
||||||
|
'leftmeta': 'super',
|
||||||
|
'rightmeta': 'super',
|
||||||
|
'enter': 'Return',
|
||||||
|
'esc': 'Escape',
|
||||||
|
'backspace': 'BackSpace',
|
||||||
|
'tab': 'Tab',
|
||||||
|
'capslock': 'Caps_Lock',
|
||||||
|
}
|
||||||
|
return special_keys.get(key_name, key_name)
|
||||||
|
|
||||||
|
def find_game_window():
|
||||||
|
"""Find the TapTapLoot window on the game display"""
|
||||||
|
try:
|
||||||
|
env = get_xenv()
|
||||||
|
result = subprocess.getoutput(
|
||||||
|
f"DISPLAY={TARGET_DISPLAY} XAUTHORITY={env.get('XAUTHORITY', '')} "
|
||||||
|
f"xdotool search --name '{TARGET_WINDOW_NAME}'"
|
||||||
|
)
|
||||||
|
if not result.strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
for window_id in result.strip().split('\n'):
|
||||||
|
window_name = subprocess.getoutput(
|
||||||
|
f"DISPLAY={TARGET_DISPLAY} XAUTHORITY={env.get('XAUTHORITY', '')} "
|
||||||
|
f"xdotool getwindowname {window_id}"
|
||||||
|
).strip()
|
||||||
|
if window_name == TARGET_WINDOW_NAME:
|
||||||
|
return window_id
|
||||||
|
|
||||||
|
# No exact match, return first result
|
||||||
|
return result.strip().split('\n')[0]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def send_keys_to_game(keys):
|
||||||
|
"""Send batched keypresses to the game window"""
|
||||||
|
if not keys or not BongoWindow:
|
||||||
|
return
|
||||||
|
|
||||||
|
env = get_xenv()
|
||||||
|
batch_size = 4
|
||||||
|
for i in range(0, len(keys), batch_size):
|
||||||
|
batch = keys[i:i+batch_size]
|
||||||
|
|
||||||
|
cmd_keydown = ['xdotool', 'keydown', '--window', BongoWindow] + batch
|
||||||
|
cmd_keyup = ['xdotool', 'keyup', '--window', BongoWindow] + batch
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd_keydown, capture_output=True, text=True, env=env)
|
||||||
|
if result.returncode != 0:
|
||||||
|
continue
|
||||||
|
time.sleep(0.01)
|
||||||
|
subprocess.run(cmd_keyup, capture_output=True, text=True, env=env)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def replace_duplicate_keys(keys):
|
||||||
|
if not keys:
|
||||||
|
return []
|
||||||
|
replacement_pool = list('abcdefghijklmnopqrstuvwxyz')
|
||||||
|
seen = set()
|
||||||
|
result = []
|
||||||
|
for key in keys:
|
||||||
|
if key in seen:
|
||||||
|
for replacement in replacement_pool:
|
||||||
|
if replacement not in seen:
|
||||||
|
result.append(replacement)
|
||||||
|
seen.add(replacement)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
result.append(key)
|
||||||
|
else:
|
||||||
|
result.append(key)
|
||||||
|
seen.add(key)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def batch_sender():
|
||||||
|
while not stop_event.is_set():
|
||||||
|
time.sleep(0.2)
|
||||||
|
keys = []
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
key = key_queue.get_nowait()
|
||||||
|
keys.append(key)
|
||||||
|
except queue.Empty:
|
||||||
|
break
|
||||||
|
if keys:
|
||||||
|
replaced_keys = replace_duplicate_keys(keys)
|
||||||
|
send_keys_to_game(replaced_keys)
|
||||||
|
|
||||||
|
def is_keyboard(device):
|
||||||
|
capabilities = device.capabilities()
|
||||||
|
if ecodes.EV_KEY not in capabilities:
|
||||||
|
return False
|
||||||
|
keys = capabilities[ecodes.EV_KEY]
|
||||||
|
mouse_buttons = [
|
||||||
|
ecodes.BTN_LEFT, ecodes.BTN_RIGHT, ecodes.BTN_MIDDLE,
|
||||||
|
ecodes.BTN_SIDE, ecodes.BTN_EXTRA, ecodes.BTN_FORWARD,
|
||||||
|
ecodes.BTN_BACK, ecodes.BTN_MOUSE,
|
||||||
|
]
|
||||||
|
for btn in mouse_buttons:
|
||||||
|
if btn in keys:
|
||||||
|
return False
|
||||||
|
if ecodes.BTN_TOUCH in keys or ecodes.BTN_TOOL_FINGER in keys:
|
||||||
|
return False
|
||||||
|
keyboard_keys = [
|
||||||
|
ecodes.KEY_A, ecodes.KEY_B, ecodes.KEY_C,
|
||||||
|
ecodes.KEY_SPACE, ecodes.KEY_ENTER, ecodes.KEY_ESC,
|
||||||
|
]
|
||||||
|
for key in keyboard_keys:
|
||||||
|
if key in keys:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def monitor_keyboard(device):
|
||||||
|
try:
|
||||||
|
for event in device.read_loop():
|
||||||
|
if keyboard_stop_event.is_set() or stop_event.is_set():
|
||||||
|
break
|
||||||
|
if event.type == ecodes.EV_KEY and event.value == 1:
|
||||||
|
try:
|
||||||
|
key_name = get_xdotool_keyname(event.code)
|
||||||
|
key_queue.put(key_name)
|
||||||
|
except (KeyError, ValueError):
|
||||||
|
pass
|
||||||
|
except (OSError, IOError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def start_keyboard_monitoring():
|
||||||
|
global keyboard_threads, keyboard_devices
|
||||||
|
keyboard_stop_event.clear()
|
||||||
|
all_devices = [InputDevice(path) for path in evdev.list_devices()]
|
||||||
|
keyboards = [device for device in all_devices if is_keyboard(device)]
|
||||||
|
if not keyboards:
|
||||||
|
print(" [Warning] No keyboard devices found")
|
||||||
|
return False
|
||||||
|
print(f" Found {len(keyboards)} keyboard device(s)")
|
||||||
|
keyboard_devices = keyboards
|
||||||
|
keyboard_threads = []
|
||||||
|
for kb in keyboards:
|
||||||
|
thread = threading.Thread(target=monitor_keyboard, args=(kb,), daemon=True)
|
||||||
|
thread.start()
|
||||||
|
keyboard_threads.append(thread)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def stop_keyboard_monitoring():
|
||||||
|
global keyboard_threads, keyboard_devices
|
||||||
|
keyboard_stop_event.set()
|
||||||
|
for kb in keyboard_devices:
|
||||||
|
try:
|
||||||
|
kb.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
for thread in keyboard_threads:
|
||||||
|
thread.join(timeout=1.0)
|
||||||
|
keyboard_threads = []
|
||||||
|
keyboard_devices = []
|
||||||
|
|
||||||
|
def window_monitor():
|
||||||
|
global BongoWindow
|
||||||
|
last_window_state = None
|
||||||
|
|
||||||
|
while not stop_event.is_set():
|
||||||
|
new_window = find_game_window()
|
||||||
|
|
||||||
|
if new_window and not last_window_state:
|
||||||
|
BongoWindow = new_window
|
||||||
|
print(f"\n✓ Connected to {TARGET_WINDOW_NAME} window (ID: {BongoWindow})")
|
||||||
|
print(" Starting keyboard monitoring...")
|
||||||
|
if start_keyboard_monitoring():
|
||||||
|
print(" ✓ Keyboard monitoring started\n")
|
||||||
|
last_window_state = True
|
||||||
|
|
||||||
|
elif not new_window and last_window_state:
|
||||||
|
print(f"\n⚠ {TARGET_WINDOW_NAME} window closed")
|
||||||
|
print(" Stopping keyboard monitoring...")
|
||||||
|
stop_keyboard_monitoring()
|
||||||
|
print(" ✓ Waiting for window to reappear...\n")
|
||||||
|
BongoWindow = None
|
||||||
|
last_window_state = False
|
||||||
|
|
||||||
|
elif not new_window and last_window_state is None:
|
||||||
|
print(f"⚠ {TARGET_WINDOW_NAME} window not found")
|
||||||
|
print(" Waiting... (checks every 10s)\n")
|
||||||
|
BongoWindow = None
|
||||||
|
last_window_state = False
|
||||||
|
|
||||||
|
elif new_window and last_window_state:
|
||||||
|
if BongoWindow != new_window:
|
||||||
|
BongoWindow = new_window
|
||||||
|
print(f"\n✓ Window ID updated (ID: {BongoWindow})\n")
|
||||||
|
|
||||||
|
stop_event.wait(10)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(f"BongoCatXP - TapTapLoot edition\n")
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
print("\nStopping...")
|
||||||
|
stop_event.set()
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
|
||||||
|
xauth = get_xauth_path()
|
||||||
|
if xauth:
|
||||||
|
print(f"Using xauth: {xauth}")
|
||||||
|
else:
|
||||||
|
print("⚠ Warning: no taptaploot xauth file found in /tmp — is the game running?")
|
||||||
|
|
||||||
|
print("Starting window monitor (Ctrl+C to stop)\n")
|
||||||
|
|
||||||
|
sender_thread = threading.Thread(target=batch_sender, daemon=True)
|
||||||
|
sender_thread.start()
|
||||||
|
|
||||||
|
monitor_thread = threading.Thread(target=window_monitor, daemon=True)
|
||||||
|
monitor_thread.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
stop_event.wait()
|
||||||
|
finally:
|
||||||
|
print("Stopping keyboard monitoring...")
|
||||||
|
stop_keyboard_monitoring()
|
||||||
|
print("Done")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog='bongocatxp',
|
||||||
|
description='BongoCat X11 Proxy - TapTapLoot edition'
|
||||||
|
)
|
||||||
|
parser.add_argument('--version', action='version', version=f'BongoCatXP {__version__}')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not shutil.which('xdotool'):
|
||||||
|
print("Error: xdotool not found — install with: sudo pacman -S xdotool")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
if not os.access('/dev/input/event0', os.R_OK):
|
||||||
|
print("No permission to read input devices.")
|
||||||
|
print("Run: sudo usermod -aG input $USER (then log out and back in)")
|
||||||
|
print(f"Or temporarily: sudo python3 {__file__}")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nError: {e}")
|
||||||
36
pyproject.toml
Normal file
36
pyproject.toml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "taptaploot-forwarder"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "TapTapLoot X11 Forwarder - Forward keyboard input to TapTapLoot on Wayland via Xwayland"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.7"
|
||||||
|
license = {text = "MIT"}
|
||||||
|
authors = [
|
||||||
|
{name = "TapTapLoot Forwarder Contributors"}
|
||||||
|
]
|
||||||
|
keywords = ["taptaploot", "x11", "keyboard", "wayland", "evdev", "xwayland"]
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
"License :: OSI Approved :: MIT License",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Topic :: Utilities",
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"evdev>=1.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
taptaploot-forwarder = "main:main"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Repository = "https://gitea.example.com/yourname/TapTapLootForwarder"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
py-modules = ["main"]
|
||||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
evdev==1.9.2
|
||||||
Reference in New Issue
Block a user