dotfiles/dot_i3/i3-barak-tool.py
2024-05-18 19:08:15 -07:00

151 lines
3.6 KiB
Python
Executable file

#!/usr/bin/env python3
import json
import subprocess
import socket
import struct
import sys
_struct_header = '=6sII'
RUN_COMMAND = 0
GET_WORKSPACES = 1
SUBSCRIBE = 4
GET_OUTPUTS=3
GET_TREE = 4
def get_i3_socketpath():
socketpath = subprocess.check_output(["i3", "--get-socketpath"])
return socketpath.decode().rstrip()
def format_i3_command(kind: int, cmd: str):
b = cmd.encode()
return b"i3-ipc" + struct.pack("=II", len(b), kind) + b
def _ipc_recv(sock):
data = sock.recv(14)
if len(data) == 0:
raise EOFError('got EOF from ipc socket')
_, msg_length, msg_type = _unpack_header(data)
msg_size = 6 + msg_length
while len(data) < msg_size:
data += sock.recv(msg_length)
payload = _unpack(data)
return payload, msg_type
def _unpack(data):
_, msg_length, _ = _unpack_header(data)
size = struct.calcsize(_struct_header)
msg_size = size + msg_length
payload = data[size:msg_size]
return payload.decode('utf-8', 'replace')
def _unpack_header(data: bytes):
return struct.unpack(_struct_header, data[:struct.calcsize(_struct_header)])
def open_i3_socket():
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
client.connect(get_i3_socketpath())
return client
def send_command(client, kind, data):
client.sendall(format_i3_command(kind, data))
result, _ = _ipc_recv(client)
return json.loads(result)
class TreeNode:
def __init__(self, json):
self.id = None
self.rect = None
self.focused = False
self.name = ""
for k, v in json.items():
if k != "nodes":
self.__setattr__(k, v)
self.parent = None
self.nodes = list(map(TreeNode, json["nodes"]))
for n in self.nodes:
n.parent = self
def find(self, where=None):
if where is None:
where = lambda _: True
if where(self):
yield self
for n in self.nodes:
yield from n.find(where)
def __repr__(self) -> str:
return "TreeNode(%d, %s, focused=%s, rect=%s)" % (self.id, self.name, self.focused, str(self.rect))
def find_parent(self, where=None):
if self.parent is None:
return None
if where is None or where(self.parent):
return self.parent
return self.parent.find_parent(where)
def leaves(self):
if len(self.nodes) == 0:
yield self
else:
for n in self.nodes:
yield from n.leaves()
def find_biggest_window(tree: TreeNode):
max_leaf = None
max_area = 0
max_x = -1
for leaf in tree.leaves():
rect = leaf.rect
area = rect["width"] * rect["height"]
if area > max_area or (area == max_area and max_x > rect["x"]):
max_area = area
max_leaf = leaf
max_x = rect["x"]
return max_leaf
def current_workspace(tree):
focused = list(tree.find(where=lambda n: n.focused is True))
if len(focused) == 0:
print("not found")
sys.exit(1)
focused = focused[0]
ws = focused.find_parent(lambda n: n.type == "workspace")
return ws
def promote(client):
json = send_command(client, GET_TREE, "")
tree = TreeNode(json)
ws = current_workspace(tree)
master = find_biggest_window(ws)
send_command(client, RUN_COMMAND, "swap container with con_id %s" % master.id)
def main():
if len(sys.argv) < 2:
print("needs an argument")
return
command = sys.argv[1]
client = open_i3_socket()
if command == "promote":
promote(client)
client.close()
if __name__ == "__main__":
main()