Skip to content

Instantly share code, notes, and snippets.

@DerEchteJoghurt
Forked from rafrombrc/open-float.py
Last active November 1, 2025 13:55
Show Gist options
  • Select an option

  • Save DerEchteJoghurt/9cabbcf8eb7b4af25e4519feda302cb2 to your computer and use it in GitHub Desktop.

Select an option

Save DerEchteJoghurt/9cabbcf8eb7b4af25e4519feda302cb2 to your computer and use it in GitHub Desktop.

Revisions

  1. DerEchteJoghurt revised this gist Nov 1, 2025. 1 changed file with 21 additions and 2 deletions.
    23 changes: 21 additions & 2 deletions open-float.py
    Original file line number Diff line number Diff line change
    @@ -1,10 +1,8 @@
    #!/usr/bin/python3
    """
    Like open-float, but dynamically. Floats a window when it matches the rules.
    Some windows don't have the right title and app-id when they open, and only set
    them afterward. This script is like open-float for those windows.
    Usage: fill in the RULES array below, then run the script.
    """

    @@ -40,6 +38,7 @@ class Rule:
    exclude: list[Match] = field(default_factory=list)
    width: int = 0
    height: int = 0
    centered: bool = False

    def matches(self, window):
    if len(self.match) > 0 and not any(m.matches(window) for m in self.match):
    @@ -72,6 +71,12 @@ def matches(self, window):
    # window-rule {} with one match that sets window size.
    # Rule([Match(title="Bitwarden", app_id="firefox")],
    # width=500, height=1000),

    Rule([Match(title=r"^Extension:\s*\(Bitwarden Password Manager\)\s*-\s*—\s*Mozilla Firefox$", app_id="firefox")],
    width=500, height=800, centered=True),

    Rule([Match(title=r"^Extension:\s*\(Open in Browser\)\s*-\s*—\s*Mozilla Firefox$", app_id="firefox")],
    width=600, height=360, centered=True),
    ]


    @@ -110,6 +115,18 @@ def set_width(id: int, width: int):
    send({"Action": {"SetWindowWidth":
    {"id": id, "change": {"SetFixed": width}}}})

    def set_centered(id: int, width: int, height: int):
    # move window to center of the screen
    send({"Action": {"MoveFloatingWindow":
    {"id": id,
    "x": {"SetProportion": 50.0},
    "y": {"SetProportion": 50.0}}}})

    # adjust for top left origin
    send({"Action": {"MoveFloatingWindow":
    {"id": id,
    "x": {"AdjustFixed": -(width / 2)},
    "y": {"AdjustFixed": -(height / 2)}}}})

    def update_matched(win):
    win["matched"] = False
    @@ -130,6 +147,8 @@ def update_matched(win):
    set_height(win["id"], r.height)
    if r.width != 0:
    set_width(win["id"], r.width)
    if r.centered:
    set_centered(win["id"], r.width, r.height)


    for line in file:
  2. @rafrombrc rafrombrc revised this gist Oct 15, 2025. 1 changed file with 0 additions and 2 deletions.
    2 changes: 0 additions & 2 deletions open-float.py
    Original file line number Diff line number Diff line change
    @@ -127,10 +127,8 @@ def update_matched(win):
    print(f"floating title={win['title']}, app_id={win['app_id']}")
    float(win["id"])
    if r.height != 0:
    print(f"height={r.height}")
    set_height(win["id"], r.height)
    if r.width != 0:
    print(f"width={r.width}")
    set_width(win["id"], r.width)


  3. @rafrombrc rafrombrc revised this gist Oct 15, 2025. 1 changed file with 1 addition and 3 deletions.
    4 changes: 1 addition & 3 deletions open-float.py
    Original file line number Diff line number Diff line change
    @@ -71,9 +71,7 @@ def matches(self, window):

    # window-rule {} with one match that sets window size.
    # Rule([Match(title="Bitwarden", app_id="firefox")],
    # width=500, heigth=1000),
    Rule([Match(title="Extension.*Bitwarden", app_id="firefox")],
    width=500, height=1000),
    # width=500, height=1000),
    ]


  4. @rafrombrc rafrombrc created this gist Oct 15, 2025.
    151 changes: 151 additions & 0 deletions open-float.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,151 @@
    #!/usr/bin/python3
    """
    Like open-float, but dynamically. Floats a window when it matches the rules.
    Some windows don't have the right title and app-id when they open, and only set
    them afterward. This script is like open-float for those windows.
    Usage: fill in the RULES array below, then run the script.
    """

    from dataclasses import dataclass, field
    import json
    import os
    import re
    from socket import AF_UNIX, SHUT_WR, socket


    @dataclass(kw_only=True)
    class Match:
    title: str | None = None
    app_id: str | None = None

    def matches(self, window):
    if self.title is None and self.app_id is None:
    return False

    matched = True

    if self.title is not None:
    matched &= re.search(self.title, window["title"]) is not None
    if self.app_id is not None:
    matched &= re.search(self.app_id, window["app_id"]) is not None

    return matched


    @dataclass
    class Rule:
    match: list[Match] = field(default_factory=list)
    exclude: list[Match] = field(default_factory=list)
    width: int = 0
    height: int = 0

    def matches(self, window):
    if len(self.match) > 0 and not any(m.matches(window) for m in self.match):
    return False
    if any(m.matches(window) for m in self.exclude):
    return False

    return True


    # Write your rules here. One Rule() = one window-rule {}.
    RULES = [
    # window-rule {} with one match.
    # Rule([Match(title="Bitwarden", app_id="firefox")]),

    # window-rule {} with one match and one exclude.
    # Rule(
    # [Match(title="rs")],
    # exclude=[Match(app_id="Alacritty")],
    # ),

    # window-rule {} with two matches.
    # Rule(
    # [
    # Match(app_id="^foot$"),
    # Match(app_id="^mpv$"),
    # ]
    # ),

    # window-rule {} with one match that sets window size.
    # Rule([Match(title="Bitwarden", app_id="firefox")],
    # width=500, heigth=1000),
    Rule([Match(title="Extension.*Bitwarden", app_id="firefox")],
    width=500, height=1000),
    ]


    if len(RULES) == 0:
    print("fill in the RULES list, then run the script")
    exit()


    niri_socket = socket(AF_UNIX)
    niri_socket.connect(os.environ["NIRI_SOCKET"])
    file = niri_socket.makefile("rw")

    _ = file.write('"EventStream"')
    file.flush()
    niri_socket.shutdown(SHUT_WR)

    windows = {}


    def send(request):
    with socket(AF_UNIX) as niri_socket:
    niri_socket.connect(os.environ["NIRI_SOCKET"])
    file = niri_socket.makefile("rw")
    _ = file.write(json.dumps(request))
    file.flush()


    def float(id: int):
    send({"Action": {"MoveWindowToFloating": {"id": id}}})

    def set_height(id: int, height: int):
    send({"Action": {"SetWindowHeight":
    {"id": id, "change": {"SetFixed": height}}}})

    def set_width(id: int, width: int):
    send({"Action": {"SetWindowWidth":
    {"id": id, "change": {"SetFixed": width}}}})


    def update_matched(win):
    win["matched"] = False
    if existing := windows.get(win["id"]):
    win["matched"] = existing["matched"]

    matched_before = win["matched"]
    for r in RULES:
    matched = r.matches(win)
    if matched:
    # first match wins
    win["matched"] = matched
    break
    if win["matched"] and not matched_before:
    print(f"floating title={win['title']}, app_id={win['app_id']}")
    float(win["id"])
    if r.height != 0:
    print(f"height={r.height}")
    set_height(win["id"], r.height)
    if r.width != 0:
    print(f"width={r.width}")
    set_width(win["id"], r.width)


    for line in file:
    event = json.loads(line)

    if changed := event.get("WindowsChanged"):
    for win in changed["windows"]:
    update_matched(win)
    windows = {win["id"]: win for win in changed["windows"]}
    elif changed := event.get("WindowOpenedOrChanged"):
    win = changed["window"]
    update_matched(win)
    windows[win["id"]] = win
    elif changed := event.get("WindowClosed"):
    del windows[changed["id"]]