i3-launch-or-focus 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. #!/usr/bin/env python3
  2. """
  3. Launch an application or focus it if already running in i3.
  4. Usage: i3-launch-or-focus --class CLASS <command> [args...]
  5. Examples:
  6. i3-launch-or-focus --class sdm-connect alacritty -e ~/.local/bin/sdm-ui.sh
  7. i3-launch-or-focus --class dropdown alacritty
  8. """
  9. import argparse
  10. import json
  11. import subprocess
  12. import sys
  13. def find_window(tree, instance=None, title=None):
  14. """Recursively search the i3 tree for a window matching criteria."""
  15. props = tree.get("window_properties", {})
  16. if instance and props.get("instance", "").lower() == instance.lower():
  17. return tree.get("id")
  18. if title and tree.get("name", "") == title:
  19. return tree.get("id")
  20. for node in tree.get("nodes", []) + tree.get("floating_nodes", []):
  21. result = find_window(node, instance=instance, title=title)
  22. if result:
  23. return result
  24. return None
  25. def focus_window(instance=None, title=None):
  26. """Focus a window using i3-msg criteria."""
  27. if instance:
  28. criteria = f'[instance="{instance}"]'
  29. elif title:
  30. criteria = f'[title="{title}"]'
  31. else:
  32. return
  33. subprocess.run(
  34. ["i3-msg", f"{criteria} focus"],
  35. stdout=subprocess.DEVNULL,
  36. stderr=subprocess.DEVNULL,
  37. )
  38. def launch_command(command, args):
  39. """Launch a command, replacing this process."""
  40. import os
  41. os.execvp(command, [command] + args)
  42. def main():
  43. parser = argparse.ArgumentParser(
  44. description="Launch an application or focus it if already running in i3"
  45. )
  46. parser.add_argument("command", help="Command to run")
  47. parser.add_argument("--class", dest="class_name", help="Window instance name")
  48. parser.add_argument("--title", help="Window title")
  49. parser.add_argument(
  50. "args", nargs=argparse.REMAINDER, help="Additional arguments for command"
  51. )
  52. args = parser.parse_args()
  53. try:
  54. result = subprocess.run(
  55. ["i3-msg", "-t", "get_tree"],
  56. capture_output=True,
  57. text=True,
  58. check=False,
  59. )
  60. tree = json.loads(result.stdout)
  61. except (json.JSONDecodeError, subprocess.SubprocessError):
  62. tree = {}
  63. window_id = find_window(tree, instance=args.class_name, title=args.title)
  64. if window_id:
  65. focus_window(instance=args.class_name, title=args.title)
  66. else:
  67. cmd_args = []
  68. if args.class_name:
  69. cmd_args.append(f"--class={args.class_name}")
  70. if args.title:
  71. cmd_args.append(f"--title={args.title}")
  72. cmd_args.extend(args.args)
  73. launch_command(args.command, cmd_args)
  74. if __name__ == "__main__":
  75. main()