Last active
May 25, 2016 20:06
-
-
Save quark-zju/ca7de775bc8584c5db90d326a53f5cfe to your computer and use it in GitHub Desktop.
i3-scriptable with auto-splitv script (emulating i3 3.x behavior)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# tl;dr Bring back most of the column behavior of i3 3.x / wmii, in i3 4.x | |
# | |
# Auto run "split v" on new (or moved) windows which is the child | |
# of the top-level container and has "splith" layout. | |
# | |
# For example, | |
# | |
# +---------+ +---------+ +---------+ +---------+ +----+----+ | |
# | | |.........| | | | | | |....| | |
# | | 1 |.........| 2 | | 3 +---------+ 4 | |....| | |
# | | > |.........| > +---------+ > | | > +----+....| | |
# | | |.........| |.........| +---------+ | |....| | |
# | | |.........| |.........| |.........| | |....| | |
# +---------+ +=========+ +---------+ +---------+ +----+====+ | |
# | |
# 1. Create a new window in an empty workspace, the window will have | |
# "split v" layout, regardless of "default_orientation" option. | |
# 2. Create a second window, no auto "split v" this time. | |
# 3. Create another one, works as expected. | |
# 4. Move the window right. It will have an auto "split v" since it is | |
# the direct child of the "split h" workspace container (new column). | |
# | |
# The difference with the column behavior is that moving a bottom window in | |
# a column down will implicitly create a "split v" parent container. | |
on_window_change ['new', 'move'] do |i3, reply| | |
node = reply.container | |
workspace = i3.workspace_of(node) | |
container = i3.inner_container_of(workspace) | |
parent = i3.parent_of(node) | |
if parent == container && parent.layout == 'splith' | |
i3.logger.info "split v for \"#{node.name}\"" | |
i3.command "[con_id=#{node.id}] split v" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
source 'http://rubygems.org' | |
gem 'i3ipc', '~> 0.2.0' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Make i3 more flexible by executing custom scripts on window change. | |
# Support script live reloading. | |
# | |
# gem 'i3ipc', github: 'veelenga/i3ipc-ruby', ref: '3316cd03c3ff2a0f8d96c01cf7abef4e70d09a06' | |
require 'i3ipc' | |
require 'logger' | |
require 'forwardable' | |
class I3Script | |
attr_accessor :i3, :script_paths | |
extend Forwardable | |
def_delegators :@i3, :command, :workspaces, :outputs, :version, :bar_config | |
def initialize | |
self.i3 ||= I3Ipc::Connection.new | |
end | |
def tree | |
@tree ||= build_tree! | |
end | |
def parent_of(id) | |
get_tree_index(id, :parent) | |
end | |
def workspace_of(id) | |
get_tree_index(id, :workspace) | |
end | |
def focused | |
build_tree | |
@tree_focused | |
end | |
def windows | |
build_tree | |
@tree_windows | |
end | |
# get first container which has >= 2 nodes | |
def inner_container_of(node) | |
return nil if node.nil? || !node.respond_to?(:nodes) | |
if node.nodes.size == 1 && node.nodes.first.window.nil? | |
inner_container_of(node.nodes.first) | |
else | |
node | |
end | |
end | |
def run(script_paths) | |
self.script_paths = [*script_paths] | |
reload_scripts! | |
event_loop if !@on_window_change_handlers.empty? | |
end | |
def finalize | |
i3.close | |
self.i3 = nil | |
end | |
def logger | |
@logger ||= Logger.new(STDERR).tap do |l| | |
l.level = (ENV['LOG'] || Logger::INFO).to_i | |
end | |
end | |
def on_window_change(change = nil, &cb) | |
@on_window_change_handlers << {change: change, callback: cb} if cb | |
end | |
# human-friendly string for an i3 node | |
def inspect_node(node = tree, recursive = true, indent = 0) | |
result = "#{' ' * indent}#{node.id} \"#{node.name}\" layout=#{node.layout} type=#{node.type} window=#{node.window}" | |
node.nodes.each {|n| result += "\n" + inspect_node(n, true, indent + 1)} if recursive | |
result | |
end | |
private | |
def event_loop | |
# Use a new connection for window change event handling | |
I3Ipc::Connection.new.tap do |i| | |
pid = i.subscribe('window', self.method(:run_window_change_handlers)) | |
pid.join | |
i.close | |
end | |
end | |
def reload_scripts! | |
@on_window_change_handlers = [] | |
@script_mtimes = {} | |
script_paths.each do |path| | |
logger.debug "Loading #{path}" | |
@script_mtimes[path] = File.mtime(path) | |
binding.eval File.read(path), path | |
end | |
end | |
def reload_scripts | |
if script_paths.any? { |path| !File.exists?(path) || @script_mtimes[path] != File.mtime(path) } | |
logger.debug "Script change detected. Reloading." | |
reload_scripts! | |
end | |
end | |
def run_window_change_handlers reply | |
expire_tree! | |
reload_scripts | |
logger.debug "Handling #{reply.change} event" | |
@on_window_change_handlers.each do |h| | |
begin | |
if h[:change].nil? || [*h[:change]].include?(reply.change) | |
h[:callback][self, reply] | |
end | |
rescue => ex | |
logger.warn ex | |
end | |
end | |
end | |
def build_tree! | |
@tree = i3.tree | |
@tree_index = {} | |
@tree_windows = [] | |
@tree_focused = nil | |
# make mappings not directly in the tree: id -> (parent, workspace) | |
dfs = proc do |node, parent, workspace| | |
@tree_index[node.id] = {parent: parent, node: node, workspace: workspace} | |
begin | |
@tree_focused = node if node.respond_to?(:focused) && node.focused | |
@tree_windows << node if node.respond_to?(:window) && node.window | |
workspace = node if node.type == 'workspace' | |
node.nodes.each {|n| dfs[n, node, workspace]} | |
node.floating_nodes.each {|n| dfs[n, node, workspace]} | |
rescue | |
end | |
end | |
dfs[@tree, nil] | |
@tree | |
end | |
def build_tree | |
build_tree! if @tree.nil? | |
end | |
def get_tree_index(id, field) | |
id = id.id if id.respond_to?(:id) | |
build_tree | |
info = @tree_index[id] | |
info && info[field] | |
end | |
def expire_tree! | |
@tree = @tree_index = @tree_focused = @tree_windows = @tree_focused = nil | |
end | |
end | |
I3Script.new.tap do |i3s| | |
begin | |
i3s.run ARGV | |
ensure | |
i3s.finalize | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment