niri-launch-or-focus 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. #!/usr/bin/env python3
  2. """
  3. Simple script to launch an application or focus it if already running in niri
  4. Usage: niri-launch-or-focus <command> [--class CLASS] [--title TITLE] [args...]
  5. Examples:
  6. niri-launch-or-focus firefox
  7. niri-launch-or-focus code --class Code --title "My Project"
  8. niri-launch-or-focus kitty --class kitty
  9. """
  10. import argparse
  11. import json
  12. import subprocess
  13. import sys
  14. def find_window(class_name=None, title=None, app_name=None):
  15. """Find window by app_id (class), title, or app name."""
  16. try:
  17. result = subprocess.run(
  18. ["niri", "msg", "--json", "windows"],
  19. capture_output=True,
  20. text=True,
  21. check=False,
  22. )
  23. windows_json = result.stdout.strip()
  24. if not windows_json or windows_json == "[]":
  25. return None
  26. windows = json.loads(windows_json)
  27. for window in windows:
  28. app_id = window.get("app_id", "").lower()
  29. if class_name:
  30. if app_id == class_name.lower():
  31. return window.get("id")
  32. elif title:
  33. if window.get("title") == title:
  34. return window.get("id")
  35. elif app_name:
  36. if app_id == app_name.lower():
  37. return window.get("id")
  38. return None
  39. except (json.JSONDecodeError, subprocess.SubprocessError, KeyError):
  40. return None
  41. def focus_window(window_id):
  42. """Focus a window by ID."""
  43. subprocess.run(
  44. ["niri", "msg", "action", "focus-window", "--id", str(window_id)],
  45. stdout=subprocess.DEVNULL,
  46. stderr=subprocess.DEVNULL,
  47. )
  48. def launch_command(command, args):
  49. """Launch a command in the background."""
  50. subprocess.Popen(
  51. [command] + args,
  52. stdout=subprocess.DEVNULL,
  53. stderr=subprocess.DEVNULL,
  54. start_new_session=True,
  55. )
  56. def main():
  57. parser = argparse.ArgumentParser(
  58. description="Launch an application or focus it if already running in niri"
  59. )
  60. parser.add_argument("command", help="Command to run")
  61. parser.add_argument("--class", dest="class_name", help="Window class (app_id)")
  62. parser.add_argument("--title", help="Window title")
  63. parser.add_argument("args", nargs=argparse.REMAINDER, help="Additional arguments for command")
  64. args = parser.parse_args()
  65. # Extract app name from command (basename)
  66. app_name = args.command.split("/")[-1]
  67. # Try to find existing window
  68. window_id = find_window(
  69. class_name=args.class_name,
  70. title=args.title,
  71. app_name=app_name if not args.class_name and not args.title else None,
  72. )
  73. # Build command arguments
  74. cmd_args = []
  75. if args.class_name:
  76. cmd_args.append(f"--class={args.class_name}")
  77. if args.title:
  78. cmd_args.append(f"--title={args.title}")
  79. cmd_args.extend(args.args)
  80. # Focus if found, otherwise launch
  81. if window_id:
  82. focus_window(window_id)
  83. else:
  84. launch_command(args.command, cmd_args)
  85. if __name__ == "__main__":
  86. main()