initial commit
This commit is contained in:
commit
3d676babd5
3 changed files with 168 additions and 0 deletions
24
LICENSE
Normal file
24
LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2025 Daniel Fichtinger <daniel@ficd.sh>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
1. Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
3. Neither the name of the author nor the names of its contributors may
|
||||||
|
be used to endorse or promote products derived from this software
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||||
|
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||||
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||||
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
37
README.md
Normal file
37
README.md
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# foot scripts
|
||||||
|
|
||||||
|
[foot]: https://codeberg.org/dnkl/foot
|
||||||
|
[`wtype`]: https://github.com/atx/wtype
|
||||||
|
|
||||||
|
This repository contains scripts for [foot].
|
||||||
|
|
||||||
|
## `foot-command`
|
||||||
|
|
||||||
|
This script implements a command palette for Foot. It allows you to
|
||||||
|
interactively pick a command defined in your `foot.ini` keybindings, and
|
||||||
|
simulate its corresponding keypress with [`wtype`]. Here's how it works:
|
||||||
|
|
||||||
|
1. Parses keybindings from the `[key-bindings]` section of `foot.ini`.
|
||||||
|
2. Pipes the list of commands to a picker and waits for a selection.
|
||||||
|
3. Sends the mapped keypress to to the focused window using `wtype`.
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
- [`wtype`].
|
||||||
|
- A dmenu-compatible picker (e.g. `fuzzel`).
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```sh
|
||||||
|
foot-command.py [-c PATH_TO_CONFIG] [-p PICKER_COMMAND]
|
||||||
|
```
|
||||||
|
|
||||||
|
I recommend binding this to a key in your window manager. You need to invoke the
|
||||||
|
command when Foot is the focused window.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
- `-c`, `--config`: Path to your `foot.ini` file. Defaults to
|
||||||
|
`$HOME/.config/foot/foot.ini`.
|
||||||
|
- `-p`, `--picker`: Picker to use (must support `dmenu` style input). Defaults
|
||||||
|
to `fuzzel --dmenu --placeholder=Select a command:`.
|
107
foot-command.py
Executable file
107
foot-command.py
Executable file
|
@ -0,0 +1,107 @@
|
||||||
|
#!/bin/env python3
|
||||||
|
|
||||||
|
# Depends: wtype
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
ENV_CONFIG = "FOOT_CONFIG"
|
||||||
|
ENV_PICKER = "FOOT_PICKER"
|
||||||
|
DEFAULT_CONFIG = "$HOME/.config/foot.ini"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_config(path: str):
|
||||||
|
config = ConfigParser(allow_unnamed_section=True)
|
||||||
|
config.read(path)
|
||||||
|
section = "key-bindings"
|
||||||
|
pairs: list[tuple[str, str]] = config.items(section, raw=True)
|
||||||
|
out: dict[str, str] = {}
|
||||||
|
for pair in pairs:
|
||||||
|
command = pair[0]
|
||||||
|
bindings = pair[1].split(" ")
|
||||||
|
if bindings[0] == "none":
|
||||||
|
continue
|
||||||
|
elif bindings[0][0] == "[":
|
||||||
|
out[command] = bindings[1]
|
||||||
|
else:
|
||||||
|
out[command] = bindings[0]
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def get_query_string(mapping: dict[str, str]) -> str:
|
||||||
|
return "\n".join(mapping.keys())
|
||||||
|
|
||||||
|
|
||||||
|
def spawn_picker(cmd, query_string):
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
selection = process.communicate(input=query_string.encode("UTF-8"))
|
||||||
|
if process.returncode != 2:
|
||||||
|
return selection[0].decode("UTF-8").strip("\n")
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
MOD_MAP = {
|
||||||
|
"Control": "ctrl",
|
||||||
|
"Shift": "shift",
|
||||||
|
"Mod1": "alt",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_wtype_args(selection: str, mapping: dict[str, str]):
|
||||||
|
binding = mapping[selection]
|
||||||
|
args: list[str] = ["wtype"]
|
||||||
|
for k in binding.split("+"):
|
||||||
|
if k in MOD_MAP:
|
||||||
|
args += ["-M", MOD_MAP[k]]
|
||||||
|
else:
|
||||||
|
args += ["-k", k]
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def send_keys(args):
|
||||||
|
process = subprocess.run(args)
|
||||||
|
process.check_returncode()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_path(path: str) -> str:
|
||||||
|
if path == DEFAULT_CONFIG:
|
||||||
|
return str(os.environ["HOME"]) + "/.config/foot/foot.ini"
|
||||||
|
else:
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = ArgumentParser()
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--config",
|
||||||
|
required=False,
|
||||||
|
help="Absolute path to foot.ini file. (default: %(default)s)",
|
||||||
|
default=DEFAULT_CONFIG,
|
||||||
|
type=str,
|
||||||
|
metavar=ENV_CONFIG,
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--picker",
|
||||||
|
required=False,
|
||||||
|
help="Picker command to be used. (default: %(default)s)",
|
||||||
|
type=str,
|
||||||
|
default="fuzzel --dmenu --placeholder=Select a command:",
|
||||||
|
metavar=ENV_PICKER,
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
picker = args.picker
|
||||||
|
foot_path = validate_path(args.config)
|
||||||
|
mapping = parse_config(foot_path)
|
||||||
|
query = get_query_string(mapping)
|
||||||
|
selection = spawn_picker(picker, query)
|
||||||
|
if selection is not None:
|
||||||
|
wtype_args = get_wtype_args(selection, mapping)
|
||||||
|
print(wtype_args)
|
||||||
|
send_keys(wtype_args)
|
Loading…
Add table
Add a link
Reference in a new issue