nscratch 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. #!/usr/bin/env python3
  2. # Adapted from the many ideas shared at: https://github.com/YaLTeR/niri/discussions/329
  3. import argparse
  4. import json
  5. import os
  6. import subprocess
  7. import sys
  8. # the found scratchpad window (id, workspace_id, is_focused, is_floating)
  9. scratch_window = {}
  10. # the focused workspace data
  11. focused_workspace = {}
  12. # the scratchpad workspace name
  13. scratch_workspace = os.getenv("NS_WORKSPACE", "scratch")
  14. def niri_cmd(cmd_args):
  15. subprocess.run(["niri", "msg", "action"] + cmd_args)
  16. def move_window_to_scratchpad(window_id, animations):
  17. niri_cmd(["move-window-to-workspace", "--window-id", str(window_id), scratch_workspace, "--focus=false"])
  18. if animations:
  19. niri_cmd(["move-window-to-tiling", "--id", str(window_id)])
  20. def bring_scratchpad_window_to_focus(window_id, args):
  21. niri_cmd(["move-window-to-workspace", "--window-id", str(window_id), str(focused_workspace["idx"])])
  22. if args.animations and not scratch_window["is_floating"]:
  23. niri_cmd(["move-window-to-floating", "--id", str(window_id)])
  24. niri_cmd(["focus-window", "--id", str(window_id)])
  25. def find_scratch_window(args, windows):
  26. for window in windows:
  27. if window["app_id"] == args.app_id:
  28. scratch_window["id"] = window["id"]
  29. scratch_window["workspace_id"] = window["workspace_id"]
  30. scratch_window["is_focused"] = window["is_focused"]
  31. scratch_window["is_floating"] = window["is_floating"]
  32. break
  33. def fetch_focused_workspace():
  34. props = subprocess.run(
  35. ["niri", "msg", "--json", "workspaces"],
  36. capture_output=True,
  37. text=True,
  38. )
  39. workspaces = json.loads(props.stdout)
  40. # get the focused workspace
  41. for workspace in workspaces:
  42. if workspace["is_focused"]:
  43. focused_workspace["idx"] = workspace["idx"]
  44. focused_workspace["output"] = workspace["output"]
  45. return workspace["id"]
  46. def ns(parser):
  47. args = parser.parse_args()
  48. props = subprocess.run(
  49. ["niri", "msg", "--json", "windows"],
  50. capture_output=True,
  51. text=True,
  52. )
  53. windows = json.loads(props.stdout)
  54. find_scratch_window(args, windows)
  55. # scratchpad does not yet exist, spawn?
  56. if not scratch_window:
  57. if args.spawn:
  58. niri_cmd(["spawn", "--"] + args.spawn.split(' '))
  59. sys.exit(0)
  60. else:
  61. parser.print_help()
  62. sys.exit(1)
  63. window_id = scratch_window["id"]
  64. # the scratchpad window exists and it's focused
  65. if not scratch_window["is_focused"]:
  66. workspace_id = fetch_focused_workspace()
  67. # the window is not in the focused workspace
  68. if scratch_window["workspace_id"] != workspace_id:
  69. bring_scratchpad_window_to_focus(window_id, args)
  70. return
  71. move_window_to_scratchpad(window_id, args.animations)
  72. def main():
  73. parser = argparse.ArgumentParser(prog='nscratch', description='Niri Scratchpad support')
  74. parser.add_argument('-id', '--app-id', required=True, help='The application identifier')
  75. parser.add_argument('-s', '--spawn', help='The process name to spawn when non-existing')
  76. parser.add_argument('-a', '--animations', action='store_true', help='Enable animations')
  77. ns(parser)
  78. if __name__ == "__main__":
  79. main()