forked from justbuchanan/i3scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
i3splat.py
188 lines (151 loc) · 5.47 KB
/
i3splat.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#!/usr/bin/env python3
#
# github.com/justbuchanan/i3scripts
#
# This module provides a compact way to specify layouts for i3wm and launch the
# corresponding programs. Create a Workspace object with the containers and apps
# you want, then call launch().
#
# Here's an example program:
#
# from i3splat import *
# mydir = "~/src"
# ws = Workspace("code", [
# (0.5, chrome(["stackoverflow.com"])),
# (0.5, Container(SPLITV, [
# (0.7, Container(TABBED, [
# (0.5, urxvt(wdir=mydir, command="vim file.txt")),
# (0.5, sublime([mydir])),
# ])),
# (0.3, urxvt(wdir=mydir)),
# ])
# )])
# ws.launch()
#
# Instead of calling launch(), you can also print out the i3 layout json
# representation for debugging purposes:
#
# print(ws.serialize_i3layout())
#
# Customization:
#
# * This script is mostly independent of the other ones in this repository. You
# will just need to modify or replace the Workspace.launch() method. See
# comments in that method for more info.
#
# * This module provides functions for a few common apps, but you will likely
# want to add your own. See the ones at the bottom of this file as examples.
import subprocess
import os
import json
import time
import shlex
import sys
class Node:
def __init__(self):
self.type = 'con'
# layout types
SPLITH = 'splith'
SPLITV = 'splitv'
STACKED = 'stacked'
TABBED = 'tabbed'
class Container(Node):
def __init__(self, layout, nodes_and_percents):
super().__init__()
self.layout = layout
self.nodes = _flatten_tuples(nodes_and_percents)
class App(Node):
def __init__(self, xClass, xInstance=None, command=None):
super().__init__()
self.command = command
self.swallows = [{'class': '^%s$' % xClass}]
if xInstance != None:
self.swallows[0]['instance'] = '^%s$' % xInstance
class Workspace:
def __init__(self, name, nodes_and_percents):
self.name = name
self.nodes = _flatten_tuples(nodes_and_percents)
# Main entry point.
def launch(self):
# Find an unused workspace number and jump to it.
from new_workspace import new_workspace
new_workspace()
# Rename the new workspace. Note that you may want to remove this or
# replace it with your own script if you're not also using the
# autoname_workspaces script in this directory.
from rename_workspace import rename_workspace
rename_workspace(self.name)
self.load_i3layout()
self.run_apps()
def iterate_apps(self):
def _iterate_node(node):
if isinstance(node, App):
yield node
else:
for child in node.nodes:
yield from _iterate_node(child)
for node in self.nodes:
yield from _iterate_node(node)
def serialize_i3layout(self):
return '\n\n'.join([
json.dumps(n, cls=WorkspaceJSONEncoder, indent=4)
for n in self.nodes
])
def load_i3layout(self):
fname = "/tmp/i3layout.json"
with open(fname, "w") as f:
f.write(self.serialize_i3layout())
subprocess.check_call(["i3-msg", "append_layout", fname])
# If delay is provided, wait a small amount of time between each app launch
# to give it time to load its window. This helps ensure that they get placed
# in the appropriate containers for apps that can't be launched with a
# custom instance name.
def run_apps(self, delay=None):
for app in self.iterate_apps():
if app.command != None:
app.command()
if delay != None:
time.sleep(delay)
class WorkspaceJSONEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, App):
a = obj.__dict__.copy()
# The 'command' property is used by this python program to launch
# the app, but it doesn't go in the i3 layout file.
del a['command']
return a
elif isinstance(obj, Container):
return obj.__dict__
return json.JSONEncoder.default(self, obj)
# Flatten a list of (percent, Node) tuples to just [Node] with the percent
# property set on each.
def _flatten_tuples(pairs):
nodes = []
for p in pairs:
n = p[1]
n.percent = p[0]
nodes.append(n)
return nodes
# Apps
################################################################################
def _cmd(args):
joined = ' '.join(args)
print("$ %s" % joined)
return lambda: subprocess.Popen(joined, shell=True)
# Note: specify the name property to ensure that i3 can uniquely match each one.
# Otherwise it's left up to chance that they launch windows in the order that
# they are called, which may not always be the case.
def urxvt(wdir="~", command=None, name="urxvt", interactive=True):
shell = os.environ["SHELL"]
wdir = os.path.expanduser(wdir)
args = ["urxvt", "-cd", shlex.quote(wdir), "-name", name, "-e", shell]
if command != None:
args.append("-c")
args.append(shlex.quote("%s; %s -i" % (command, shell)))
return App(command=_cmd(args), xClass="URxvt", xInstance=name)
def chrome(urls):
browser = ["google-chrome-stable", "--new-window"]
return App(xClass="Google-chrome", command=_cmd(browser + urls))
def sublime(paths):
quoted_paths = [shlex.quote(p) for p in paths]
return App(command=_cmd(["subl", "-n"] + quoted_paths), xClass="Subl")