z_mirror/dotfiles

git clone https://git.raiguard.me/z_mirror/dotfiles

Caleb HeuerAdd sway autotiling script880f5bc

noctalia
8.2 KiB215 linesraw
1#!/usr/bin/env python3
2
3"""
4This script uses the i3ipc python module to switch the layout splith / splitv
5for the currently focused window, depending on its dimensions.
6It works on both sway and i3 window managers.
7
8Inspired by https://github.com/olemartinorg/i3-alternating-layout
9
10Copyright: 2019-2021 Piotr Miller & Contributors
11e-mail: nwg.piotr@gmail.com
12Project: https://github.com/nwg-piotr/autotiling
13License: GPL3
14
15Dependencies: python-i3ipc>=2.0.1 (i3ipc-python)
16"""
17import argparse
18import os
19import sys
20from functools import partial
21
22from i3ipc import Connection, Event
23
24try:
25    from .__about__ import __version__
26except ImportError:
27    __version__ = "unknown"
28
29
30def temp_dir():
31    return os.getenv("TMPDIR") or os.getenv("TEMP") or os.getenv("TMP") or "/tmp"
32
33
34def save_string(string, file_path):
35    try:
36        with open(file_path, "wt") as file:
37            file.write(string)
38    except Exception as e:
39        print(e)
40
41
42def output_name(con):
43    if con.type == "root":
44        return None
45
46    if p := con.parent:
47        if p.type == "output":
48            return p.name
49        else:
50            return output_name(p)
51
52
53def switch_splitting(i3, e, debug, outputs, workspaces, depth_limit, splitwidth, splitheight, splitratio):
54    try:
55        con = i3.get_tree().find_focused()
56        output = output_name(con)
57        # Stop, if outputs is set and current output is not in the selection
58        if outputs and output not in outputs:
59            if debug:
60                print(f"Debug: Autotiling turned off on output {output}", file=sys.stderr)
61            return
62
63        if con and not workspaces or (str(con.workspace().num) in workspaces):
64            if con.floating:
65                # We're on i3: on sway it would be None
66                # May be 'auto_on' or 'user_on'
67                is_floating = "_on" in con.floating
68            else:
69                # We are on sway
70                is_floating = con.type == "floating_con"
71
72            if depth_limit:
73                # Assume we reached the depth limit, unless we can find a workspace
74                depth_limit_reached = True
75                current_con = con
76                current_depth = 0
77                while current_depth < depth_limit:
78                    # Check if we found the workspace of the current container
79                    if current_con.type == "workspace":
80                        # Found the workspace within the depth limitation
81                        depth_limit_reached = False
82                        break
83
84                    # Look at the parent for next iteration
85                    current_con = current_con.parent
86
87                    # Only count up the depth, if the container has more than
88                    # one container as child
89                    if len(current_con.nodes) > 1:
90                        current_depth += 1
91
92                if depth_limit_reached:
93                    if debug:
94                        print("Debug: Depth limit reached")
95                    return
96
97            is_full_screen = con.fullscreen_mode == 1
98            is_stacked = con.parent.layout == "stacked"
99            is_tabbed = con.parent.layout == "tabbed"
100
101            # Exclude floating containers, stacked layouts, tabbed layouts and full screen mode
102            if (not is_floating
103                    and not is_stacked
104                    and not is_tabbed
105                    and not is_full_screen):
106                new_layout = "splitv" if con.rect.height > con.rect.width / splitratio else "splith"
107
108                if new_layout != con.parent.layout:
109                    result = i3.command(new_layout)
110                    if result[0].success and debug:
111                        print(f"Debug: Switched to {new_layout}", file=sys.stderr)
112                    elif debug:
113                        print(f"Error: Switch failed with err {result[0].error}", file=sys.stderr)
114
115                if e.change in ["new", "move"] and con.percent:
116                    if con.parent.layout == "splitv" and splitheight != 1.0:  # top / bottom
117                        # print(f"split top fac {splitheight*100}")
118                        i3.command(f"resize set height {int(con.percent * splitheight * 100)} ppt")
119                    elif con.parent.layout == "splith" and splitwidth != 1.0:  # top / bottom:                     # left / right
120                        # print(f"split right fac {splitwidth*100} ")
121                        i3.command(f"resize set width {int(con.percent * splitwidth * 100)} ppt")
122
123        elif debug:
124            print("Debug: No focused container found or autotiling on the workspace turned off", file=sys.stderr)
125
126    except Exception as e:
127        print(f"Error: {e}", file=sys.stderr)
128
129
130def get_parser():
131    parser = argparse.ArgumentParser(prog="autotiling", description="Script for sway and i3 to automatically switch the horizontal / vertical window split orientation")
132
133    parser.add_argument("-d", "--debug", action="store_true",
134                        help="print debug messages to stderr")
135    parser.add_argument("-v", "--version", action="version",
136                        version=f"%(prog)s {__version__}, Python {sys.version}",
137                        help="display version information")
138    parser.add_argument("-o", "--outputs", nargs="*", type=str, default=[],
139                        help="restricts autotiling to certain output; example: autotiling --output  DP-1 HDMI-0")
140    parser.add_argument("-w", "--workspaces", nargs="*", type=str, default=[],
141                        help="restricts autotiling to certain workspaces; example: autotiling --workspaces 8 9")
142    parser.add_argument("-l", "--limit", type=int, default=0,
143                        help='limit how often autotiling will split a container; '
144                             'try "2" if you like master-stack layouts; default: 0 (no limit)')
145    parser.add_argument("-sw",
146                        "--splitwidth",
147                        help='set the width of the vertical split (as factor); default: 1.0;',
148                        type=float,
149                        default=1.0, )
150    parser.add_argument("-sh",
151                        "--splitheight",
152                        help='set the height of the horizontal split (as factor); default: 1.0;',
153                        type=float,
154                        default=1.0, )
155    parser.add_argument("-sr",
156                        "--splitratio",
157                        help='Split direction ratio - based on window height/width; default: 1;'
158                             'try "1.61", for golden ratio - window has to be 61%% wider for left/right split; default: 1.0;',
159                        type=float,
160                        default=1.0, )
161
162    """
163    Changing event subscription has already been the objective of several pull request. To avoid doing this again
164    and again, let's allow to specify them in the `--events` argument.
165    """
166    parser.add_argument("-e", "--events", nargs="*", type=str, default=["WINDOW", "MODE"],
167                        help="list of events to trigger switching split orientation; default: WINDOW MODE")
168
169    return parser
170
171def main():
172    args = get_parser().parse_args()
173
174    if args.debug:
175        if args.outputs:
176            print(f"autotiling is only active on outputs: {','.join(args.outputs)}")
177        if args.workspaces:
178            print(f"autotiling is only active on workspaces: {','.join(args.workspaces)}")
179
180    # For use w/ nwg-panel
181    ws_file = os.path.join(temp_dir(), "autotiling")
182    if args.workspaces:
183        save_string(','.join(args.workspaces), ws_file)
184    else:
185        if os.path.isfile(ws_file):
186            os.remove(ws_file)
187
188    if not args.events:
189        print("No events specified", file=sys.stderr)
190        sys.exit(1)
191
192    handler = partial(
193        switch_splitting,
194        debug=args.debug,
195        outputs=args.outputs,
196        workspaces=args.workspaces,
197        depth_limit=args.limit,
198        splitwidth=args.splitwidth,
199        splitheight=args.splitheight,
200        splitratio=args.splitratio
201    )
202    i3 = Connection()
203    for e in args.events:
204        try:
205            i3.on(Event[e], handler)
206            print(f"{Event[e]} subscribed")
207        except KeyError:
208            print(f"'{e}' is not a valid event", file=sys.stderr)
209
210    i3.main()
211
212
213if __name__ == "__main__":
214    main()
215