├── src ├── args.yml └── main.rs ├── .gitignore ├── .github └── workflows │ ├── build.yml │ ├── build-i3.yml │ ├── release-i3.yml │ └── release.yml ├── Cargo.toml ├── LICENSE └── README.md /src/args.yml: -------------------------------------------------------------------------------- 1 | name: sway-alttab 2 | about: Simple Alt-Tab daemon for SwayWM. Switches back to previous focused window on Alt-Tab or SIGUSR1 3 | author: Govind KP 4 | 5 | args: 6 | - combo: 7 | help: Custom key combo for switching to last window [Sway/i3 format] (defaults to "Mod1+Tab") 8 | short: c 9 | long: combo 10 | takes_value: true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | # create by https://github.com/iamcco/coc-gitignore (Sat Dec 07 2019 04:18:05 GMT+0530 (India Standard Time)) 5 | # Rust.gitignore: 6 | # Generated by Cargo 7 | # will have compiled files and executables 8 | /target/ 9 | 10 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 11 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 12 | Cargo.lock 13 | 14 | # These are backup files generated by rustfmt 15 | **/*.rs.bk 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v1 12 | 13 | - name: Cargo check 14 | run: cargo check --verbose 15 | 16 | test: 17 | name: Test 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout sources 21 | uses: actions/checkout@v1 22 | 23 | - name: Cargo test 24 | run: cargo test --verbose 25 | 26 | build: 27 | name: Build 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v1 32 | - name: Build 33 | run: cargo build --verbose 34 | -------------------------------------------------------------------------------- /.github/workflows/build-i3.yml: -------------------------------------------------------------------------------- 1 | name: build-i3 2 | 3 | on: [push] 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout sources 11 | uses: actions/checkout@v1 12 | with: 13 | ref: i3 14 | 15 | - name: Cargo check 16 | run: cargo check --verbose 17 | 18 | test: 19 | name: Test 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout sources 23 | uses: actions/checkout@v1 24 | with: 25 | ref: i3 26 | 27 | - name: Cargo test 28 | run: cargo test --verbose 29 | 30 | build: 31 | name: Build 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v1 36 | with: 37 | ref: i3 38 | - name: Build 39 | run: cargo build --verbose 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sway-alttab" 3 | description = "Simple Alt-Tab daemon for SwayWM/i3. Switches back to previous focused window on SIGUSR1" 4 | version = "1.1.2" 5 | authors = ["Govind KP "] 6 | repository = "https://github.com/reisub0/sway-alttab" 7 | readme = "./README.md" 8 | license = "MIT" 9 | edition = "2018" 10 | categories = ["command-line-utilities"] 11 | keywords = ["sway", "swayipc", "i3"] 12 | 13 | [badges] 14 | is-it-maintained-issue-resolution = { repository = "reisub0/sway-alttab" } 15 | is-it-maintained-open-issues = { repository = "reisub0/sway-alttab" } 16 | maintenance = { status = "actively-developed" } 17 | 18 | [profile.release] 19 | codegen-units = 1 20 | lto = true 21 | 22 | [dependencies] 23 | swayipc = {"version" = "^2.2.3"} 24 | signal-hook = {"version" = "^0.1"} 25 | daemonize = { "version" = "^0.4" } 26 | 27 | [dependencies.clap] 28 | version = "2.33.0" 29 | features = ["yaml"] 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Govind KP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sway-alttab 2 | 3 | [![crates.io](https://flat.badgen.net/crates/v/sway-alttab)](https://crates.io/crates/sway-alttab) [![crates.io](https://flat.badgen.net/crates/d/sway-alttab)](https://crates.io/crates/sway-alttab) [![Build Status](https://flat.badgen.net/github/checks/reisub0/sway-alttab?label=build)](https://github.com/reisub0/sway-alttab/actions?query=workflow%3Abuild) [![made-with-rust](https://flat.badgen.net/badge/made%20with%20♥/rust/dea584)](https://www.rust-lang.org/) 4 | 5 | A simple daemon that keeps track of your last focused window and switches to it on receiving a SIGUSR1. Automatically binds Alt-Tab to the same action. 6 | 7 | To change the key combo from "Mod1+Tab", use the `-c` flag and set your preferred mapping. 8 | 9 | ## Installation 10 | 11 | ### From Binaries 12 | 13 | Binary releases (for Linux x86_64) can be found at the [Releases](https://github.com/reisub0/sway-alttab/releases/latest) page. 14 | 15 | ### From AUR 16 | 17 | If you're using Arch Linux, you can install the `sway-alttab-bin` package. 18 | ```bash 19 | yay -S --aur sway-alttab-bin 20 | ``` 21 | 22 | ### From Crates.io 23 | 24 | ```bash 25 | cargo install sway-alttab 26 | ``` 27 | 28 | ### From Source 29 | 30 | ```bash 31 | git clone https://github.com/reisub0/sway-alttab 32 | cargo install --path sway-alttab 33 | ``` 34 | 35 | ## License 36 | 37 | sway-alttab is licensed under the [MIT License](https://choosealicense.com/licenses/mit/). 38 | -------------------------------------------------------------------------------- /.github/workflows/release-i3.yml: -------------------------------------------------------------------------------- 1 | name: release-i3 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+' 7 | 8 | jobs: 9 | check: 10 | name: Check 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v1 15 | with: 16 | ref: i3 17 | 18 | - name: Cargo check 19 | run: cargo check --verbose 20 | 21 | test: 22 | name: Test 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout sources 26 | uses: actions/checkout@v1 27 | with: 28 | ref: i3 29 | 30 | - name: Cargo test 31 | run: cargo test --verbose 32 | 33 | build: 34 | name: Build 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v1 40 | with: 41 | ref: i3 42 | 43 | - name: Build 44 | run: cargo build --verbose 45 | 46 | release-build: 47 | name: Release Build 48 | runs-on: ubuntu-latest 49 | needs: [check, test, build] 50 | 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v1 54 | with: 55 | ref: i3 56 | 57 | - name: Build Release 58 | run: cargo build --release --verbose 59 | 60 | - name: Strip unnecessary symbols 61 | run: strip ./target/release/i3-alttab 62 | 63 | - name: Upload build artifact 64 | uses: actions/upload-artifact@v1 65 | with: 66 | name: binary 67 | path: ./target/release/i3-alttab 68 | 69 | release: 70 | name: Release 71 | runs-on: ubuntu-latest 72 | needs: [release-build] 73 | 74 | steps: 75 | - name: Download artifact 76 | uses: actions/download-artifact@v1 77 | with: 78 | name: binary 79 | 80 | - name: Create Release 81 | id: create_release 82 | uses: actions/create-release@v1 83 | env: 84 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 85 | with: 86 | tag_name: ${{ github.ref }} 87 | release_name: Release ${{ github.ref }} 88 | draft: false 89 | prerelease: false 90 | 91 | # - name: Print tree 92 | # shell: bash 93 | # run: | 94 | # echo HELLO 95 | # tree -CAhF --dirsfirst 96 | 97 | - name: Upload Release Asset 98 | id: upload-release-asset 99 | uses: actions/upload-release-asset@v1.0.1 100 | env: 101 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 102 | with: 103 | upload_url: ${{ steps.create_release.outputs.upload_url }} 104 | asset_path: binary/i3-alttab 105 | asset_name: i3-alttab 106 | asset_content_type: application/octet-stream 107 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+' 7 | 8 | jobs: 9 | check: 10 | name: Check 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v1 15 | 16 | - name: Cargo check 17 | run: cargo check --verbose 18 | 19 | test: 20 | name: Test 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout sources 24 | uses: actions/checkout@v1 25 | 26 | - name: Cargo test 27 | run: cargo test --verbose 28 | 29 | build: 30 | name: Build 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v1 36 | 37 | - name: Build 38 | run: cargo build --verbose 39 | 40 | release-build: 41 | name: Release Build 42 | runs-on: ubuntu-latest 43 | needs: [check, test, build] 44 | 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v1 48 | 49 | - name: Build Release 50 | run: cargo build --release --verbose 51 | 52 | - name: Strip unnecessary symbols 53 | run: strip ./target/release/sway-alttab 54 | 55 | - name: Upload build artifact 56 | uses: actions/upload-artifact@v1 57 | with: 58 | name: binary 59 | path: ./target/release/sway-alttab 60 | 61 | publish: 62 | name: Publish to Cargo 63 | runs-on: ubuntu-latest 64 | needs: [check, test] 65 | 66 | steps: 67 | - name: Login 68 | env: 69 | CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} 70 | run: cargo login $CARGO_TOKEN 71 | 72 | - name: Checkout 73 | uses: actions/checkout@v1 74 | 75 | - name: Publish to Cargo 76 | run: cargo --verbose publish 77 | 78 | release: 79 | name: Release 80 | runs-on: ubuntu-latest 81 | needs: [release-build] 82 | 83 | steps: 84 | - name: Download artifact 85 | uses: actions/download-artifact@v1 86 | with: 87 | name: binary 88 | 89 | - name: Create Release 90 | id: create_release 91 | uses: actions/create-release@v1 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | with: 95 | tag_name: ${{ github.ref }} 96 | release_name: Release ${{ github.ref }} 97 | draft: false 98 | prerelease: false 99 | 100 | # - name: Print tree 101 | # shell: bash 102 | # run: | 103 | # echo HELLO 104 | # tree -CAhF --dirsfirst 105 | 106 | - name: Upload Release Asset 107 | id: upload-release-asset 108 | uses: actions/upload-release-asset@v1.0.1 109 | env: 110 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 111 | with: 112 | upload_url: ${{ steps.create_release.outputs.upload_url }} 113 | asset_path: binary/sway-alttab 114 | asset_name: sway-alttab 115 | asset_content_type: application/octet-stream 116 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env::var; 2 | use std::fs::remove_file; 3 | use std::sync::{Arc, Mutex}; 4 | 5 | use clap::{crate_version, load_yaml, App}; 6 | use swayipc::reply::Event::Window; 7 | use swayipc::reply::WindowChange; 8 | use swayipc::{Connection, EventType}; 9 | 10 | type Res = std::result::Result>; 11 | 12 | fn get_current_focused_id() -> Res { 13 | Connection::new()? 14 | .get_tree()? 15 | .find_focused_as_ref(|n| n.focused) 16 | .map(|n| n.id) 17 | .ok_or_else(|| Err("Failed to get current Focused ID").unwrap()) 18 | } 19 | 20 | fn handle_signal(last_focused: &Arc>) -> Res<()> { 21 | Connection::new()?.run_command(format!("[con_id={}] focus", last_focused.lock().unwrap()))?; 22 | Ok(()) 23 | } 24 | 25 | fn unbind_key() -> Res<()> { 26 | let yml = load_yaml!("args.yml"); 27 | let args = App::from_yaml(yml).version(crate_version!()).get_matches(); 28 | let key_combo = args.value_of("combo").unwrap_or("Mod1+Tab"); 29 | 30 | let pid_file = format!( 31 | "{}/sway-alttab.pid", 32 | var("XDG_RUNTIME_DIR").unwrap_or("/tmp".to_string()) 33 | ); 34 | Connection::new()?.run_command(format!( 35 | "unbindsym {} exec pkill -USR1 -F {}", 36 | key_combo, pid_file 37 | ))?; 38 | Ok(()) 39 | } 40 | 41 | fn bind_key() -> Res<()> { 42 | let yml = load_yaml!("args.yml"); 43 | let args = App::from_yaml(yml).version(crate_version!()).get_matches(); 44 | let key_combo = args.value_of("combo").unwrap_or("Mod1+Tab"); 45 | 46 | let pid_file = format!( 47 | "{}/sway-alttab.pid", 48 | var("XDG_RUNTIME_DIR").unwrap_or("/tmp".to_string()) 49 | ); 50 | 51 | Connection::new()?.run_command(format!( 52 | "bindsym {} exec pkill -USR1 -F {}", 53 | key_combo, pid_file 54 | ))?; 55 | Ok(()) 56 | } 57 | 58 | fn start_daemon() -> Res<()> { 59 | let dir = var("XDG_RUNTIME_DIR").unwrap_or("/tmp".to_string()); 60 | 61 | unsafe { signal_hook::register(signal_hook::SIGTERM, cleanup)? }; 62 | unsafe { signal_hook::register(signal_hook::SIGINT, cleanup)? }; 63 | 64 | Ok(daemonize::Daemonize::new() 65 | .pid_file(format!("{}/sway-alttab.pid", dir)) 66 | .chown_pid_file(true) 67 | .working_directory(dir) 68 | .start()?) 69 | } 70 | 71 | fn cleanup() { 72 | let dir = var("XDG_RUNTIME_DIR").unwrap_or("/tmp".to_string()); 73 | remove_file(format!("{}/sway-alttab.pid", dir)).unwrap(); 74 | unbind_key().unwrap(); 75 | println!("Exiting sway-alttab"); 76 | } 77 | 78 | fn main() -> Res<()> { 79 | let last_focus = Arc::new(Mutex::new(0)); 80 | let mut cur_focus = get_current_focused_id()?; 81 | let clone = Arc::clone(&last_focus); 82 | 83 | unsafe { 84 | signal_hook::register(signal_hook::SIGUSR1, move || { 85 | handle_signal(&clone).unwrap(); 86 | })? 87 | }; 88 | 89 | start_daemon()?; 90 | 91 | bind_key()?; 92 | 93 | let subs = [EventType::Window]; 94 | let mut events = Connection::new()?.subscribe(&subs)?; 95 | 96 | loop { 97 | let event = events.next(); 98 | if let Some(Ok(Window(ev))) = event { 99 | if ev.change == WindowChange::Focus { 100 | let mut last = last_focus.lock().unwrap(); 101 | *last = cur_focus; 102 | cur_focus = ev.container.id; 103 | } 104 | } else { 105 | cleanup(); 106 | } 107 | } 108 | } 109 | --------------------------------------------------------------------------------