Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

back and forth between windows #838

Closed
i3bot opened this issue Oct 6, 2012 · 33 comments
Closed

back and forth between windows #838

i3bot opened this issue Oct 6, 2012 · 33 comments
Assignees

Comments

@i3bot
Copy link

i3bot commented Oct 6, 2012

[Originally reported by simao.m@…]
(Feature Request:

Currently, there's a command that allows the user to go back and forth between workspaces.

The same feature for windows would be very good, one common use case in a developer workflow is to go back and forth between two windows (editor/terminal for example), so this would increase productivity.

Thanks!

@stapelberg
Copy link
Member

The feature request is accepted, patches are welcome.

@i3bot
Copy link
Author

i3bot commented Dec 21, 2012

[Original comment by anonymous]

Commands to focus next window and to focus previous window would be useful, not only for that but also to be able to cycle through all the windows without having to worry about the geometrical disposition. To browse the leafs of the layout tree in a flat way, if you prefer.

There could be two variants of such a command: focus next/previous and focus next/previous visible, which would skip windows that are not visible because of tabbing and stacking.

@i3bot
Copy link
Author

i3bot commented Jan 26, 2013

[Original comment by anonymous]

I'd like to add my agreement here; having keybindings to cycle through all windows in next/previous fashion would be really useful.

@i3bot
Copy link
Author

i3bot commented Mar 19, 2013

[Original comment by tkf]

This is especially valuable now that 4.5 has changed where focus lands after moving a window to a different workspace (previously focus remained with that window, now it stays in the workspace it was moved from - very annoying IMHO). This could easily be worked around:

bindsym $mod+Shift+1 move container to workspace 1; focus previous

@i3bot
Copy link
Author

i3bot commented Mar 19, 2013

[Original comment by tkf]

Disregard. I can't read.

@i3bot
Copy link
Author

i3bot commented Apr 2, 2013

[Original comment by anonymous]

I second that! I really miss the ability to easily jump back and forth between windows, too.

Especially i think of a popup that shows a stack of the N last focused windows from which you can select one you want to jump to. That would really be a nice feature at least from my point of view...

@stapelberg
Copy link
Member

FWIW, I don’t think having this feature is a good idea anymore. The workspace back and forth stuff is quite complex and increases code paths to think about in many situations.

I’d prefer it if this could be implemented as a script using IPC instead of in core i3.

@i3bot
Copy link
Author

i3bot commented Jul 2, 2013

[Original comment by anonymous]

How would you start implementing this as a script?

@stapelberg
Copy link
Member

Replying to comment 9 by anonymous:

How would you start implementing this as a script?
By implementing a focus event, then subscribing to that event from a script (e.g. using AnyEvent::I3 in Perl or one of the Python IPC libraries) and storing the ID of the focused window whenever focus changes. I’d then bind a key to a separate script (or the same script with a flag) which would somehow trigger the long-running process to send a [con_id="xxx"] focus command.

@i3bot
Copy link
Author

i3bot commented Jul 4, 2013

[Original comment by simao.m@…]

I tried using the existing window event but, but this is only sent when a window is completed repainted, not when it gets focus.

Is there any other event I could use?

Thanks

@stapelberg
Copy link
Member

The documentation at http://build.i3wm.org/docs/ipc.html is (always) up to date.

You need to implement the event, which is trivial.

@i3bot
Copy link
Author

i3bot commented Dec 31, 2013

[Original comment by roomcays@…]

At the current state IPC does not support "window focus" event.

Focusing window in so-called "Recently used manner" would be awesome feature, I agree.
Until some smart people will create a patch for it, the only workaround I see is calling a python/perl/bash script bound to keystrokes one uses for switching windows (so all the ' * focus' binds), which saves currently focused window's ID to a file (possibly somewhere within /tmp or other TMPFS mountpoint). Then next key stroke first reads this ID, then overwrites with new, and uses so loaded window ID as a target ID of a window to switch to.

I may try to do it, and if so, I will share my solution.

@i3bot
Copy link
Author

i3bot commented Jan 2, 2014

[Original comment by anonymous]

yes, please share it if you can find a solution :) Thanks

@i3bot
Copy link
Author

i3bot commented Jan 4, 2014

[Original comment by roomcays@…]

I've tried something very basic, but solution I've provided does not work with quick window switching due to file saving lags.
The best solution would be implementation of this feature within i3 itself.

@i3bot
Copy link
Author

i3bot commented Feb 25, 2014

[Original comment by anonymous]

Any updates on this?

Given that implementing the event / hook should be trivial, I think it would be nice to have it available.

@i3bot
Copy link
Author

i3bot commented May 30, 2014

[Original comment by anonymous]

This would be a nice feature. +1

@i3bot
Copy link
Author

i3bot commented Jun 17, 2014

[Original comment by ivan@…]

It would also allow to safely avoid sending keyboard input to some floating windows that pop up while writing (e.g. XWrits). for_window [class="XWrits"] floating enable, focus tiling isn't really safe because it doesn't work if you were already in a floating container. focus last would be perfect here.

@i3bot
Copy link
Author

i3bot commented Jul 20, 2014

[Original comment by nicoe@…]

I have implemented the design explained in comment:10 in the following pull request of i3ipc-python: altdesktop/i3ipc-python#2

@mikedmcfarland
Copy link

FYI. I've also written a script that also gives this feature
http://thrownforaloop.com/posts/i3-finder/
uses i3-msg, not the ipc though.

@acrisci
Copy link
Member

acrisci commented Mar 2, 2015

This should be closed.

@stapelberg
Copy link
Member

Closing as requested.

@gvalkov
Copy link

gvalkov commented Oct 21, 2017

Hello,

I'm terribly used to awesomewm's $mod+Tab behavior (i.e. cycle between the two most recently focused windows). It would have been nice if this was in core (as implemented in #1492), but here's my take on an ipc solution.

event-listener.py:

#!/usr/bin/env python3

import os
import asyncio
import i3ipc

socket = '{XDG_RUNTIME_DIR}/i3/event-listener.sock'.format_map(os.environ)
events = asyncio.Queue()
window_stack = []

def enqueue(*args):
    events.put_nowait(args)

async def i3_event_listener():
    while True:
        conn, event = await events.get()
        if event.change == 'focus':
            window_stack.insert(0, event.container.props.id)
            if len(window_stack) > 2:
                del window_stack[2:]

async def on_unix_connect(reader, writer):
    while True:
        cmd = await reader.readline()
        cmd = cmd.strip()
        if not cmd:
            break

        if cmd == b'swap_focus' and window_stack:
            cmd = '[con_id=%s] focus' % window_stack.pop()
            i3.command(cmd)

async def main():
    if os.path.exists(socket):
        os.unlink(socket)

    server = await asyncio.start_unix_server(on_unix_connect, socket)
    asyncio.ensure_future(i3_event_listener())
    await server.wait_closed()

i3 = i3ipc.Connection()
i3.on('window::focus', enqueue)
i3.event_socket_setup()

loop = asyncio.get_event_loop()
loop.add_reader(i3.sub_socket, i3.event_socket_poll)
loop.run_until_complete(main())

i3/config

bindsym $mod+Tab exec echo "swap_focus" | nc --send-only -U "$XDG_RUNTIME_DIR/i3/event-listener.sock"
exec --no-startup-id event-listener.py

@x-ji
Copy link

x-ji commented Sep 28, 2018

@gvalkov Thanks! This is literally the biggest pain point of me using i3. I was trying to explore xmonad for this reason. Now with this working I may be able to stick with i3 for a while longer.

Note for potential future readers: The nc command here needs to be provided by nmap-netcat. Neither GNU netcat nor BSD netcat works.

@nicarran
Copy link

This script from reddit does the trick for me. I made some modifications to save the focus only if the window was "used" for some time (permanence by default 1000ms):

#!/bin/bash

epochMillis(){
	echo $(($(date +%s%N)/1000000))
}

lastTime=$(epochMillis)
permanence=1000

xprop -root -spy _NET_ACTIVE_WINDOW | 
while read line
do
	currentFocus=$(echo "$line" | awk -F' ' '{printf $NF}')
	if [ "$currentFocus" = "0x0" -o "$currentFocus" = "$prevFocus" ]
	then
		continue
	fi
	currentTime=$(epochMillis)
	period=$(($currentTime-$lastTime))
	lastTime=$currentTime
	# if the permanence (period) is too small then don't care about the focus change but
	# allow jumping between given two windows at fast speed
	if [ $period -gt $permanence  -o "$currentFocus" = "$prevPrevFocus" ]
	then
		[[ -z "$prevFocus" ]] || i3-msg "[id=$prevFocus] mark f" > /dev/null
		prevPrevFocus=$prevFocus
	fi
	prevFocus=$currentFocus
done

@archenemies
Copy link

@nicarran:

I like your modifications, although I prefer not to have the window titles change, so I put the mark name back to "_last". I bound it to Alt+l:

bindsym $mod+l [con_mark=_last] focus

Before this I tried the script from i3pc-python examples, which turned out to be pretty laggy. The Reddit script and your script are not laggy at all.

@stapelberg:

Well, I was going to say it's too bad that users have to try out different scripts to get a behavior which so many other pieces of software recognize as basic functionality. On the other hand nicarran's script with the time delay probably does it more flexibly than one would expect from a built-in feature. And in fairness I've been using i3 for several years before noticing the lack of Screen's "other" command.

Still, I don't really see why it should be considered a stain on the elegance of your software to have a built-in mark which identifies the last window focused. Perhaps "__last_focus", "__last_dwell" for nicarran's behavior, "__last_input" for some variant that looks at whether I typed something or moved the mouse. With each of these marks moving to the last focused window when the marked window is focused again. OK, that's starting to sound complicated. Still, you did accept the feature request... back in 2012... :)

@Airblader
Copy link
Member

I don't really see why […]

This argument can be applied to any feature, really. The main disadvantage here is that the more features you add, the more complex the whole system becomes. One of the benefits of i3 over some other, similar window managers is its current balance between being configurable, but not too complex. There's window managers built around as little configuration as possible, and others for achieving maximum customizability.

Still, you did accept the feature request... back in 2012... :)

And back in 2012 we had quite a different world for i3. Many things have been added since, and nowadays doing this using the IPC is easily achievable. Not to mention that everyone ought to change their mind over a timespan of 7 years… :-)

@archenemies
Copy link

But not 6 :P

@nicarran
Copy link

@archenemies, in case you are still using something like the previous script...

#!/bin/bash

queue=( )
queueLimit=6

# 1: value to push
pushOnQueue(){
	queue=( "$1" ${queue[@]:0:$queueLimit} )
}

popOnQueue(){
	queue=( ${queue[@]:1:$queueLimit} )
}

epochMillis(){
	echo $(($(date +%s%N)/1000000))
}

cleanMarkedFocus(){
	markFailed=;
	[[ -z "${queue[0]}" ]] || i3-msg "[id=${queue[0]}] mark _prevFocus" 2>/dev/null | grep -i "\"success\":false" >/dev/null && markFailed=true;
	while [ "${queue[0]}" = "$currentFocus" -o ! -z "$markFailed" ]
	do
		popOnQueue
		if [ -z "${queue[0]}" ]
		then
			break
		fi
		markFailed=;
		i3-msg "[id=${queue[0]}] mark _prevFocus" 2>/dev/null | grep -i "\"success\":false" >/dev/null && markFailed=true;
	done
}

lastTime=$(epochMillis)
permanence=1000

xprop -root -spy _NET_ACTIVE_WINDOW | 
while read line
do
	currentFocus=$(echo "$line" | awk -F' ' '{printf $NF}')
	if [ "$currentFocus" = "${queue[0]}" ]
	then
		forceNextMark=true
	fi
	cleanMarkedFocus
	if [ "$currentFocus" = "0x0" -o "$currentFocus" = "$prevFocus" ]
	then
		continue
	fi
	currentTime=$(epochMillis)
	period=$(($currentTime-$lastTime))
	lastTime=$currentTime
	# if the permanence (period) is too small then don't care about the focus change but
	# allow jumping between given two windows at fast speed
	if [ ! -z "$forceNextMark" -o $period -gt $permanence  -o "$currentFocus" = "$prevPrevFocus" ]
	then
		forceNextMark=""
		if [ ! -z "$prevFocus" ]
		then
			if i3-msg "[id=$prevFocus] mark _prevFocus" 2>/dev/null | grep -i "\"success\":true" 2>&1 1>/dev/null 
			then
				pushOnQueue "$prevFocus"
				prevPrevFocus=$prevFocus
			fi
		fi
	fi
	prevFocus=$currentFocus
done

This new version keeps a queue of previously focused windows to correct the _prevFocus mark in case its window was closed. The thing is ugly because my bash script skills are primitive but I think it does a better job.

@archenemies
Copy link

archenemies commented Feb 8, 2019

@nicarran thank you for that. It seems like the new functionality is well-motivated, I noticed myself that after closing a window the "last" keybinding stops working.

One thing that could be improved about these scripts is to use "mark --add" (see issue #2014) rather than just "mark". That way they don't interfere with other uses of marks, e.g. my "copy-and-paste windows" bindings, suggested by Orestis:

https://www.freelists.org/post/i3-discuss/is-there-a-way-to-select-a-window-for-repositioning,4

I'm running your recent version of the script (I call it "focus-last-helper-3") with this modification.

Aside from the aforementioned "mark --add" fix, if you are interested in making other changes, then I would

  1. some factoring, move all i3-msg invocations to a single function

  2. introduce all the variables at the top, this also helps if the environment happens to have one set

  3. some comments. what is cleanMarkedFocus doing? why would a mark fail, what does that imply?

I think your Bash skills are better than mine! but this is just what came to mind when looking over the script.

@nicarran
Copy link

nicarran commented Apr 10, 2019

hi @archenemies, thanks for your feedback. I changed the script again, this time because I wanted to be able to change not only to the previous window (_prevFocus0 on the new script) but also to one previous to it (_prevFocus2):

#!/bin/bash

# epochMillis: print the amount of millis since epoch time on the std output stream.
epochMillis(){
	echo $(($(date +%s%N)/1000000))
}

# queue: array holding the focused window ids in order, the most recent focus is on index 0. 
queue=( )
queueLimit=8

# pushOnQueue: insert a window id on index 0 of the queue. This function also removes the given id if was already present, the queue is an ordered set.
# arg 1: window id to insert.
pushOnQueue(){
	if [ "$1" = "0x0" ]
	then
		return
	fi
	newQueue=( "$1" )
	for i in "${queue[@]}"
	do
		if [ "$1" = "$i" ]
		then
			continue
		fi
		newQueue=( ${newQueue[@]:0} "$i" )
	done
	queue=( "${newQueue[@]:0:$queueLimit}" )
}

# markQueue: put i3 marks using the window ids stored on the queue. The windows are marked using "_prevFocusX" where X is a integer corresponding with the index of the element on the queue. The previously focused window (the most recent) is marked using "_prevFocus0". This function also cleans the queue from windows that can't be marked (e.g. windows that have been closed).
markQueue(){
	queueCopy=( "${queue[@]}" )
	queue=( )
	j=0
	for i in "${queueCopy[@]}"
	do
		i3-msg "unmark _prevFocus$j" 1>/dev/null 2>&1
		if i3-msg "[id=$i] mark --add _prevFocus$j" 2>/dev/null | grep -i "\"success\":true" 1>/dev/null 2>&1 
		then
			queue=( ${queue[@]:0} "$i" )
			j=$(($j + 1))
		fi
	done
}

lastTime=$(epochMillis)
# permanence: period in millis used to determine if a window is worthy to be saved on the queue. This enables i3 window fast navigation without saving unworthy window ids.
permanence=1000

# main loop: read activated window ids continuously. 
xprop -root -spy _NET_ACTIVE_WINDOW | 
while read line
do
	
	currentTime=$(epochMillis)
	period=$(($currentTime-$lastTime))
	lastTime=$currentTime
	
	# previousFocus: window id of the previously focused (activated) window.
	previousFocus=$currentFocus
	# currentFocus: window id of the window that has just being activated.
	currentFocus=$(echo "$line" | awk -F' ' '{printf $NF}')
	
	# push the previousFocus id to the queue if the time spent on the previous window was greater than permanence. Check also to allow fast switching between two windows.
	if [ $period -gt $permanence -o "$currentFocus" = "${queue[0]}" ] 
	then
		pushOnQueue "$previousFocus"
	fi

	# if the currentFocus is marked as the previous window (_prevFocus0 or queue[0]) then swap the first two elements on the queue to allow switching.
	if [ "${queue[0]}" = "$currentFocus" ]
	then
		queue=( ${queue[1]} ${queue[0]} ${queue[@]:2} )
	fi

	markQueue

done

The script is still ugly but this time I added some comments and tried to follow some of your advice :-)

For reference, my i3 config has the following lines for shortcuts to switch to the _prevFocus and _prevFocus2 windows:

bindsym $mod+comma [con_mark=_prevFocus0] focus
bindsym $mod+ctrl+comma [con_mark=_prevFocus2] focus

Cheers!

UPDATE: bug fix: command was redirecting to "@1" instead of "&1"
UPDATE: bug fix: changed "2>&1 1>/dev/null" to "1>/dev/null 2>&1"

@olivierlemoal
Copy link

olivierlemoal commented Nov 15, 2019

I updated @gvalkov python script to make it work with the latest i3ipc version : https://github.com/olivierlemoal/i3-swap-focus

@medwatt
Copy link

medwatt commented Aug 23, 2022

In case anyone is still interested, I was also looking for this feature. I ended up implementing one myself. Here's the link. This script does two things.

  1. It cycles back and forth between windows. However, please note that, while using it, I noticed that the floating windows in the form of popups, dialog boxes (which are the most frequent of floating windows), would render this feature useless. As a result, the code ignores floating windows. It only remembers the last non-floating window.

  2. The second feature is even more useful for people with multi-monitor setup. Basically, I implemented a workspace_back_and_forth that works like the built-in command except that it remembers the last visited workspace on each monitor. The default behavior does not take multiple monitors into account.

@c02y
Copy link

c02y commented Mar 19, 2023

@medwatt the file of your link is gone.

focus prev/next only works for windows inside a container or only between floating windows, it cannot switch focus between normal tiling window and floating window nor windows across workspaces.

Is it possible to switch focus based on the window ids of focused time, no matter where workspaces they are or no matter they are tiling windows or floating windows?

@orestisfl orestisfl closed this as not planned Won't fix, can't repro, duplicate, stale Jan 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests