From ee31ea8b757fe8f651940161b715cad2038bb516 Mon Sep 17 00:00:00 2001 From: knotteye Date: Sat, 14 Mar 2020 07:34:54 -0500 Subject: [PATCH] Initial Commit --- .gitignore | 1 + LICENSE | 13 +++++ Makefile | 19 +++++++ README.md | 28 +++++++++++ src/swayfocus.cr | 125 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 src/swayfocus.cr diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f783f1c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +swayfocus diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ff9e935 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified +copies of this license document, and changing it is allowed as long +as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d2c70c1 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +ifeq ($(PREFIX),) + PREFIX := /usr/local +endif + +all: release + +.PHONY: clean +clean: + rm -f swayfocus + +.PHONY: run +run: + crystal run src/swayfocus.cr + +release: + crystal build --release --no-debug src/swayfocus.cr + +install: + install swayfocus $(PREFIX)/bin/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9524d3d --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# SWAYFOCUS - Window Switching Tool For Sway + +A tool to focus a specific window in sway, useful for run or raise scripts that would have used xdotool or wmctrl under Xorg. +Uses swaymsg to communicate. Windows hidden to the system tray don't show up in the get_tree command, so they can't be raised with this. + +## Usage +``` +Usage: swayfocus [OPTIONS] + -v, --version Show Version + -h, --help Show Help + -p, --print Print window names and exit + -c, --cycle Cycle through all matching windows in order, instead of selecting the first in the list + -n WNAME, --name=WNAME Match against window name + -m WMARK, --mark=WMARK Match against window mark + -t WTYPE, --type=WTYPE Match against window type (app_id for wayland, class for xwayland) +``` +Select at least one matching option. + +## Examples +``` +swayfocus -p | sort -u | bemenu | xargs swayfocus -c --name= +# simple window switcher +``` + +## Installation +``` +make && sudo make install +``` diff --git a/src/swayfocus.cr b/src/swayfocus.cr new file mode 100644 index 0000000..dbc636e --- /dev/null +++ b/src/swayfocus.cr @@ -0,0 +1,125 @@ +require "json" +require "option_parser" + +version = "0.1" +puts "" + +class Window + JSON.mapping({ + id: UInt32, + name: String, + wtype: {type: String, key: "type"}, + app_id: {type: String, default: ""}, + wclass: {type: String, key: "class", default: ""}, + marks: {type: Array(String), default: [] of String}, + nodes: {type: Array(Window), default: [] of Window}, + focused: {type: Bool, default: false} + }) + + def clear_nodes + @nodes = [] of Window + end +end + +def node_loop (root : Window) + list = [] of Window + root.nodes.each do |node| + if(node.wtype == "con") + list << node + elsif(node.nodes != [] of Window) + list.concat(node_loop(node)) + end + end + return list +end + +client_tree = Window.from_json(`swaymsg -t get_tree`) + +if(client_tree.wtype != "root") + puts "Malformed client tree." + exit(1) +end + +window_list = node_loop(client_tree) + +def parse_marks(marks : Array(String), mark : String) + res : Bool = false + marks.each do |cm| + if(cm.includes? mark) + res = true + end + end + return !res +end + +def check_type +end + +cycle = false +OptionParser.parse do |parser| + parser.banner = "Usage: swayfocus [OPTIONS]" + + parser.on "-v", "--version", "Show Version" do + puts version + exit(0) + end + + parser.on "-h", "--help", "Show Help" do + puts parser + exit(0) + end + + parser.on "-p", "--print", "Print window names and exit" do + window_list.each do |win| + puts win.name + end + exit(0) + end + + parser.on "-c", "--cycle", "Cycle through all matching windows in order, instead of selecting the first in the list" do + cycle = true + end + + parser.on "-n WNAME", "--name=WNAME", "Match against window name" do |wname| + window_list.reject! {|w| !w.name.includes? wname} + end + + parser.on "-m WMARK", "--mark=WMARK", "Match against window mark" do |wmark| + window_list.reject! {|w| parse_marks(w.marks, wmark)} + end + + parser.on "-t WTYPE", "--type=WTYPE", "Match against window type (app_id for wayland, class for xwayland)" do |wtype| + window_list.reject! {|w| !(w.app_id.includes?(wtype) || w.wclass.includes?(wtype))} + end + + parser.invalid_option do |flag| + STDERR.puts "#{flag} is not a valid option" + STDERR.puts "" + STDERR.puts parser + exit(1) + end + + parser.missing_option do |flag| + STDERR.puts "#{flag} requires an argument" + STDERR.puts "" + STDERR.puts parser + exit(1) + end + + parser.unknown_args do + exit(0) + end +end + +if(window_list == [] of Window) + puts "No matching window." + exit(1) +end +if(cycle) + while(!window_list[window_list.size - 1].focused) + window_list.push window_list[0] + window_list.delete_at(0) + end +end + +Process.exec("swaymsg", [%<[con_id=>+window_list[0].id.to_s+%<]>,"focus"])