diff --git a/desktop.rb b/desktop.rb index 732a75f..86d7016 100644 --- a/desktop.rb +++ b/desktop.rb @@ -1,4 +1,5 @@ + class Desktop attr_accessor :name, :id, :wm, :layout, :window @@ -9,7 +10,7 @@ class Desktop def show = children.each(&:show) def hide = children.each(&:hide) def inspect = "" - def update_layout = (layout&.call if active?) + def update_layout = (layout&.call(@wm.focus) if active?) end diff --git a/floating.rb b/floating.rb new file mode 100644 index 0000000..f9e2f3b --- /dev/null +++ b/floating.rb @@ -0,0 +1,28 @@ +# +# Apart from the very limited "place" the main purpose of this +# class is to ensure there *always* is a layout +# +class FloatingLayout + + def initialize(rootgeom) + @rootgeom = rootgeom + end + + def find(w) = nil + + def place(w, focus) + attr = w.get_geometry + return if attr.is_a?(X11::Form::Error) + x = attr.x + y = attr.y + width = attr.width + height = attr.height + width = @rootgeom.width / 2 if width < 10 + height = @rootgeom.height - 100 if height < 10 + x = (@rootgeom.width - width) /2 if x == 0 + y = (@rootgeom.height - height)/2 if y == 0 + w.configure(x:, y:, width:, height:) + end + + def call = nil +end diff --git a/geom.rb b/geom.rb index 4e88344..de37674 100644 --- a/geom.rb +++ b/geom.rb @@ -5,7 +5,8 @@ module X11 def inspect = "" end end -end + end + def gap(geom,g) geom = geom.dup geom.x += g @@ -39,3 +40,33 @@ def split_geom(geom, dir, node, gap, ratio) else raise "Invalid direction" end end + +# FIXME: This needs tweaks. Especially for floating windows, where +# what we really want is to e.g. treat partially overlapping windows +# so that the one closest to *overlapping* the correct border is picked +def find_closest(w, dir, from) + return nil if from.empty? + + g = w.get_geometry rescue nil # FIXME + return nil if g.nil? + + predicate = case dir + when :left then predicate = ->(g2) { g.x - (g2.x + g2.width) } + when :right then predicate = ->(g2) { g2.x - (g.x + g.width) } + when :up then predicate = ->(g2) { g.y - (g2.y + g2.height) } + when :down then predicate = ->(g2) { g2.y - (g.y + g.height) } + end + + secondary = case dir + when :left, :right then ->(g2) { (g.y - g2.y).abs } + else ->(g2) { (g.x - g2.x).abs } + end + + return from.map do |win| + g2 = win.get_geometry rescue nil + next if g2.nil? + [predicate.call(g2), secondary.call(g2), win] + end.reject{|d1,d2, win| d1 < 0} + .min_by{|d1,d2, win| [d1,d2] }&.last +end + diff --git a/leaf.rb b/leaf.rb index ab1e4bb..4dcdffc 100644 --- a/leaf.rb +++ b/leaf.rb @@ -25,6 +25,8 @@ class Leaf if !k.member?(window) return nil if !@iclass @window = nil + else + k.delete(window) end self end diff --git a/node.rb b/node.rb index d4471fa..300fc88 100644 --- a/node.rb +++ b/node.rb @@ -30,15 +30,19 @@ class Node @nodes.length <= 1 ? @nodes.first : self end - def append(window) = @nodes << Leaf.new(window, parent: self) + def append(window) + @nodes << Leaf.new(window, parent: self) + end def place_adjacent(window, focus, dir) + l = (self.parent || self).find(window) + raise if l if nodes.length == 2 - i = nodes.index(focus) - dir ||= Node.swapdir(@dir) + i = nodes.index(focus) || 1 + dir ||= Node.swapdir(@dir) || :lr @nodes[i] = Node.new([focus, Leaf.new(window)], parent: self, dir: dir) else - @dir = dir if dir + #@dir ||= dir if dir append(window) end end diff --git a/tiled.rb b/tiled.rb index 2ee0805..cdee75a 100644 --- a/tiled.rb +++ b/tiled.rb @@ -27,14 +27,18 @@ class TiledLayout return if apply_placements(window) return @root.place(window) if !focus leaf = self.find(focus) - leaf&.parent&.place_adjacent(window, leaf, dir) || @root.place(window) + if leaf && leaf.parent + leaf.parent.place_adjacent(window, leaf, dir) + else @root.place(window) + end call + true end - def call + def call(focus=nil) new_windows = windows - @root.children cleanup - new_windows.each { place(_1) } + new_windows.each { place(_1,focus) } g = GAP/(1.3 ** @root.children.length) @root.layout(gap(@geom,g), g) end diff --git a/wm.rb b/wm.rb index 783bf8e..04fa8b3 100644 --- a/wm.rb +++ b/wm.rb @@ -1,4 +1,6 @@ +require_relative 'floating' + class WindowManager attr_reader :dpy, :desktops, :windows, :focus @@ -11,6 +13,8 @@ class WindowManager @border_normal = 0x88666666 @border_focus = 0xffff66ff + @floating = FloatingLayout.new(rootgeom) + @desktops ||= num_desktops.times.map do |num| Desktop.new(self, num, name: (num+1).to_s[-1]) end @@ -69,8 +73,10 @@ class WindowManager def current_desktop = desktops[current_desktop_id] || desktops[0] def root_id = (@root_id ||= @dpy.screens.first.root) def root = (@root ||= X11::Window.new(@dpy, root_id)) - def update_layout = current_desktop&.update_layout - + def layout = current_desktop&.layout || @floating + def layout_for(w) = (w.floating? ? @floating : layout) + def update_layout = layout.call(@focus) + # FIXME: Does not take into account panels def rootgeom = (@rootgeom ||= root.get_geometry) def window(wid) @@ -88,7 +94,6 @@ class WindowManager return w if w w = Window.new(self, wid) begin - STDERR.puts "\e[35madopt6\e[0m: #{wid.to_s(16)}; type=#{w.type.inspect}" # FIXME: At least some of these ought to "adopted" but set as # floating/non-layout so they stay on a single desktop. # @@ -102,26 +107,18 @@ class WindowManager w.type == dpy.atom(:_NET_WM_WINDOW_TYPE_SPLASH) || w.type == dpy.atom(:_NET_WM_WINDOW_TYPE_UTILITY) w.floating = true - p [:ignoring, w.inspect] w.stack return w end if w.desktop? w.floating = true end - STDERR.puts "\e[35madopt5\e[0m: #{wid.to_s(16)}" attr = w.get_window_attributes - if attr.wclass == 2 # InputOnly - # We don't want to adopt inputonly windows, as they're - # for event handling only - return w - end + return w if attr.wclass == 2 # InputOnly return w if attr.override_redirect w.mapped = attr.map_state != 0 geom = w.get_geometry - return w if geom.is_a?(X11::Form::Error) - - return w if geom.width < 2 || geom.height < 2 + return w if geom.is_a?(X11::Form::Error) || geom.width < 2 || geom.height < 2 @windows[wid] = w wms = w.get_property(:_NET_WM_STATE, :atom)&.value @@ -152,23 +149,8 @@ class WindowManager def map_window(wid) w = window(wid) - attr = w.get_geometry - return if attr.is_a?(X11::Form::Error) - x = attr.x - y = attr.y - width = attr.width - height = attr.height - width = rootgeom.width / 2 if width < 10 - height = rootgeom.height - 100 if height < 10 - w.mapped = true - if w.floating? - x = (rootgeom.width - width) /2 if x == 0 - y = (rootgeom.height - height)/2 if y == 0 - w.configure(x:, y:, width:, height:) - else - current_desktop.layout&.place(w, @focus) - end + layout_for(w).place(w, @focus) unless layout_for(w).find(w) w.map set_focus(wid) unless w.special? end @@ -204,7 +186,6 @@ class WindowManager def set_focus(wid) return if wid == root_id w = window(wid) - p [:set_focus, wid, w] # FIXME: This may be a bit brutal, in that it prevents keyboard control of the desktop or dock. return if w.special? @@ -216,52 +197,6 @@ class WindowManager change_property(:_NET_ACTIVE_WINDOW, :window, wid) end - # FIXME: This needs tweaks. Especially for floating windows, where - # what we really want is to e.g. treat partially overlapping windows - # so that the one closest to *overlapping* the correct border is picked - def find_closest(w, dir, from) - g = w.get_geometry - - case dir - when :left then predicate = ->(g2) { g.x - (g2.x + g2.width) } - when :right then predicate = ->(g2) { g2.x - (g.x + g.width) } - when :up then predicate = ->(g2) { g.y - (g2.y + g2.height) } - when :down then predicate = ->(g2) { g2.y - (g.y + g.height) } - end - - min = 10000 - list = [] - p [:here] - from.each do |win| - p [:checking, win, dir] - g2 = win.get_geometry rescue nil - next if g2.nil? - dist = predicate.call(g2).abs - if dist <= min - if dist == min - list << win - else - list = [win] - min = dist - end - end - p [dist, min, list] - end - p [min, list] - return nil if list.empty? - return list.first if list.length == 1 - - # More than one in the same direction, - # FIXME: For now we just pick the first. - # Ideally I'd probably want to request the pointer location - # and find the closest along the other axis. - # May also want to check which window had focus last, - # and track last direction, so that e.g. left->right->left - # will go back to the same window - - return list.first - end - def destroy_window(wid) if w = @windows[wid] @windows.delete(wid) @@ -308,7 +243,6 @@ class WindowManager rescue # FIXME: Why is this here? end - #p w if @start.detail == 1 # Move if w.floating? w.configure(x: @attr.x + xdiff, y: @attr.y + ydiff) @@ -326,39 +260,33 @@ class WindowManager @attr.height = @attr.height+ (tb ? -ydiff : ydiff) w.configure(x: @attr.x, y: @attr.y, width: @attr.width, height: @attr.height) else - layout = current_desktop&.layout - if layout - ancestors = ->(first,dir,flag, &block) do - first&.ancestors&.each_cons(2) do |prev, node| - if node.dir == dir && - ((node.nodes[0] == prev && !flag) || - (node.nodes[1] == prev)) - node.ratio += node.geom ? block.call(prev,node,flag) : 0.0 - node.ratio = node.ratio.clamp(0.1,0.9) - return - end + ancestors = ->(first,dir,flag, &block) do + first&.ancestors&.each_cons(2) do |prev, node| + if node.dir == dir && + ((node.nodes[0] == prev && !flag) || + (node.nodes[1] == prev)) + node.ratio += node.geom ? block.call(prev,node,flag) : 0.0 + node.ratio = node.ratio.clamp(0.1,0.9) + return end end - - ancestors.call(w.layout_leaf,:lr,lr) do |prev, node, flag| - (((node.geom.width * node.ratio) + xdiff)/node.geom.width) - node.ratio - end - - ancestors.call(w.layout_leaf, :tb, tb) do |prev,node, flag| - (((node.geom.height * node.ratio) + ydiff)/node.geom.height) - node.ratio - end - update_layout end + + ancestors.call(w.layout_leaf,:lr,lr) do |prev, node, flag| + (((node.geom.width * node.ratio) + xdiff)/node.geom.width) - node.ratio + end + + ancestors.call(w.layout_leaf, :tb, tb) do |prev,node, flag| + (((node.geom.height * node.ratio) + ydiff)/node.geom.height) - node.ratio + end + update_layout end @start.root_x = ev.root_x @start.root_y = ev.root_y end end - def on_button_release(ev) - @start.child = nil if @start - end - + def on_button_release(ev) = (@start.child = nil if @start) def on_focus_in(ev) = focus || set_focus(ev.event) def on_enter_notify(ev) = set_focus(ev.event) def on_unmap_notify(ev) = window(ev.window)&.desktop&.update_layout @@ -414,7 +342,6 @@ class WindowManager dir = dpy.get_atom_name(dir).downcase.to_sym return if !@focus || @focus.special? w = find_closest(@focus, dir, @focus.desktop.mapped_regular_children) - set_focus(w.wid) if w end @@ -422,8 +349,7 @@ class WindowManager def on_rwm_shift_direction(_,dir) # FIXME: Respect the window passed instead of doing it to @focus return if !@focus || @focus.special? - node = current_desktop&.layout&.find(@focus) - if node + if node = layout.find(@focus) node = node.parent if node.is_a?(Leaf) node.dir = node.dir == :lr ? :tb : :lr current_desktop&.update_layout @@ -435,13 +361,13 @@ class WindowManager # FIXME: Respect the window passed instead of doing it to @focus # no matter what return if !@focus || @focus.special? - node = current_desktop&.layout&.find(@focus) - if node + # FIXME: Move to layout? + if node = layout.find(@focus) node = node.parent if node.is_a?(Leaf) tmp = node.nodes[0] node.nodes[0] = node.nodes[1] node.nodes[1] = tmp - current_desktop&.update_layout + update_layout end end