Close

Fixing Alt-Tab behavior

A project log for Potpourri

Definition: A mixture of things, especially a musical or literary medley

peter-walshPeter Walsh 12/26/2016 at 19:493 Comments

I'm on a campaign to remove minor annoyances in my life, and the alt-tab behavior in Cinnamon finally annoyed me enough that I had to change it.

The new version presents the task list in a fixed and predictable order every time alt-tab is activated, which allows me to use muscle memory to quickly switch between windows.

Update1: A diff listing below shows the changes more clearly.

Update2: Firefox and Chrome titles include the web page title; in other words, their window_title(), hence their position in the list, will change depending on which webpage is accessed.

The solution might be to sort on "Mozilla" or "Chrome" if the task name contains "Mozilla" or "Chrome". Perhaps if the title contains a dash, sort on the substring after the dash character. I'll look into that later if it becomes a problem or if enough people request it.


The Problem Statement

On linux/Cinnamon (and windows), pressing <alt><tab> pops up a list of active applications, allowing the user to navigate and select a window to switch to. When a different window is selected, the current window is pushed down, and the new window comes forward.

The problem is that alt-tab sorts the windows in least-recently-used order; meaning, the window you are in is the 1st position, the one you were in last is in 2nd position, and so on.

This works for switching between two - and only two - windows, but if you need three or more it quickly becomes a permutation engine. Selecting the 4th window puts the 1st window in 2nd place, the 2nd window in 3rd place, and so on.

For example, if you simultaneously use an editor for source, a command window to build, and a third window for test (seeing the results), you cannot quickly switch between windows because the ordering is always different. You have to stop, look at the list, recognize the icons, and then tell your fingers how many times to tap the <tab> key.

Add one or two more apps (a file explorer say, or a system monitor) and it rapidly becomes an exercise in fumbling and redoing while paying attention: your fingers *want* to learn a fixed action to switch to the editor, but half the time they go somewhere else and you have to stop, look, and recover.

It's annoying as hell.


Initial Attempts

My go-to solution for these sorts of problems is to go out on the net and see if someone else has solved it, then download their solution. Usually with an "apt-get install their-solution" command.

Apparently no one has done this for Alt-Tab. So far as I can tell, there's no add-on or applet that will change the behavior.

However, in looking around the net I accidentally found a reference to this file:

/usr/share/cinnamon/js/ui/appSwitcher/appSwitcher.js

Could editing this file change the alt-tab behavior?


Step 1: Is this the right file?

Linux comes with a long list of default annoyances, and the first step in fixing any of them is to find the right file to change. This can take anywhere from 4 hours to several days, depending on on the specific annoyance you want to change.

So when I found the file reference above, I knew that there was a fair chance I could address the issue without spending a lot of time finding the right file.

Maybe.

Editing Cinnamon files is roughly akin to walking a tightrope over a canyon with no net: it's trivial to completely bork your system in such a way that a complete system reinstall is the easiest fix.

Knowing this, I felt it was *likely* that messing up alt-tab *probably* wouldn't completely bork my system, since I could reboot and put things back without activating the alt-tab function.

I've been wrong about this before. To give you an idea of how easy this is, I typo'ed the filename of the screen saver executable, and Cinnamon went into an infinite error-popup-reload cycle that was almost impossible to get out of. You would *think* that missing a screen saver would not be a critical/must reboot error, but it was.

Looking through the directory, I noticed the file "classicSwitcher.js" has the following few lines [at about line 408]:

let title = window.get_title();
if (window.minimized) {
    this.label = new St.Label({ text: "[" + title + "]"});
    let contrast_effect = new Clutter.BrightnessContrastEffect();
    contrast_effect.set_brightness(-0.5, -0.5, -0.5);
    this._iconBin.add_effect(contrast_effect);
    }

It appears that minimized windows have their title enclosed in square brackets.

Let's make a *small* change to this file and see if that affects anything. I added "zz" to the beginning of the bracket text:

    this.label = new St.Label({ text: "zz[" + title + "]"});

Silently intoning, "Our Os who art in CPU, LINUX be thy name...", I carefully invoked the incantation of window manager reload: <ctrl><alt><esc>.

Like the strike of lightning during a storm, the window flashed briefly, the speakers crackled, and the system *seemed* to be working.

...I've been fooled by that before.

With trepidation, I carefully entered <alt><tab> and viewed the results.

The rightmost icon has "zz" before the braces! And the system is stable! Woot!

Step 2: Can I change the ordering?

Going back to appSwitcher.js file, I noticed the following code snippet [about line 78]:

    // Sort by user time
    windows.sort(sortWindowsByUserTime);
    return windows;
    }

That looks promising. Let's comment that out and see what happens.

One flash of lightning and clap of thunder later (why do the speakers crackle when the desktop reloads?), and pressing alt-tab results in:

It's not obvious from the image, but the windows are now in Z-order. A little experimentation shows that they are *always* in Z-order, regardless of when they were last accessed.

Another success! Woot!

Don't get cocky! Baby steps...


Step 3: Can I fix the ordering?

The sortWindowsByUserTme function is a typical sorting callback. It compares two objects (in this case application windows) and returns -1, 0, or 1 depending on the ordering of the objects [about line 15]:

function sortWindowsByUserTime(win1, win2) {
    let t1 = win1.get_user_time();
    let t2 = win2.get_user_time();
    let m1 = win1.minimized;
    let m2 = win2.minimized;
    if (m1 == m2) {
        return (t2 > t1) ? 1 : -1;   
    }
    else {
        return m1 ? 1 : -1;
    }    
}

I want the window order fixed, so that alt-tab shows the same ordering every time it's invoked.

What I'd *like* to have is the windows shown in the same order as the taskbar at the bottom of my screen. That's apparently impossible (according to the internet), so I'll have to settle for something else.

Cinnamon source code is completely undocumented, so there's no way to tell what resources are available, or what functions can be called from the context of the app switcher. The best you can get is a list of functions available to the class being executed, and even that's hard because we don't know what that class we're in, or what the base classes are.

(I did actually find some documentation on Cinnamon, but it's apparently a different linux desktop package named "Cinnamon" that doesn't have "ClassicSwitcher" or "appSwitcher" in their reference docs. I also found a computer-generated listing of the classes, but containing no descriptive text or explanations.)

However, going back to the "zz" section, we know that window.get_title() is available to that class. The sort function is being passed two windows, so perhaps we can sort on the window title?

The new, improved code snippet reads [about line 15]:

function sortWindowsByUserTime(win1, win2) {
    let t1 = win1.get_title();
    let t2 = win2.get_title();
    return t1.localeCompare(t2);
//    let t1 = win1.get_user_time();
//    let t2 = win2.get_user_time();
//    let m1 = win1.minimized;
//    let m2 = win2.minimized;
//    if (m1 == m2) {
//        return (t2 > t1) ? 1 : -1;   
//    }
//    else {
//        return m1 ? 1 : -1;
//    }    
}

I re-enabled the call to the sorting function [originally line 78, now 84] and invoked the magical incantation of desktop reload, and...

Voila! The tasks are now sorted by title name, and the ordering is fixed. It never changes, except after adding or deleting a window.


Step 4: Fixing the default selection

The tasks are ordered by name and fixed order, but the task *after* the current task is highlighted. This means that you still can't use muscle memory to select a task, because the number of keypresses varies with your starting point.

Apparently the current task is active when the app list is invoked, and the act of pressing alt-tab moves the selection one position to the right.

This means that you can cycle through the list of applications with a single alt-tab press. Some people might prefer this, but I don't happen to like it. I want the highlighted selection to always be in the same position.

A quick perusal of the code shows that this is determined by the _currentIndex class variable, which is set when the application list is generated.

So I added a line to select the last window in the sorted list on startup, and the action of pressing alt-tab moves the selection to the first position.

Adding 1 line [at about 106]:

this._currentIndex = this._windows.indexOf(global.display.focus_window);
if (this._currentIndex < 0) {
    this._currentIndex = 0;
}
this._currentIndex = this._windows.length-1; // ADD THIS LINE
Reincant the reload and voila! A completely usable alt-tab task switcher. The tasks are presented in a fixed, unchanging order, with the leftmost task initially selected.

One press gets me to the command prompt always, two gets me to Chrome, three goes to the editor, and so on.

And this, which should be a checkbox in the preferences panel, only took a couple of hours to implement.

Not bad by linux standards!


Further research

Ideally, the user should be able to specify the application order somehow. It seems reasonable to use the taskbar ordering, and that would give the user a ready-made way to change the task list order, and the ordering would always be visible.

I'd love to be able to do that, but I don't know how to extract that information from within the Cinnamon app switcher, and as I mentioned there's no documentation anywhere.

If anyone knows how to accomplish this, please let me know in the comments.


Diff of two files

Here's a diff of appSwitcher.js with the original:

#/usr/share/cinnamon/js/ui/appSwitcher: 
diff appSwitcher.js appSwitcher.js.bak 
16,30c16,25
<     let t1 = win1.get_title();
<     let t2 = win2.get_title();
< 
<     return t1.localeCompare(t2);
< 
< //    let t1 = win1.get_user_time();
< //    let t2 = win2.get_user_time();
< //    let m1 = win1.minimized;
< //    let m2 = win2.minimized;
< //    if (m1 == m2) {
< //        return (t2 > t1) ? 1 : -1;   
< //    }
< //    else {
< //        return m1 ? 1 : -1;
< //    }    
---
>     let t1 = win1.get_user_time();
>     let t2 = win2.get_user_time();
>     let m1 = win1.minimized;
>     let m2 = win2.minimized;
>     if (m1 == m2) {
>         return (t2 > t1) ? 1 : -1;   
>     }
>     else {
>         return m1 ? 1 : -1;
>     }    
106,107d100
<         this._currentIndex = this._windows.length-1;

Discussions

Alfonso wrote 09/07/2020 at 12:37 point

I finally found someone with the same opinion about Alt-Tab in linux! 

Really good research. I do not expect this to be implemented in Cinnamon, but now I have a patch to apply after reinstallations :-D

  Are you sure? yes | no

Peter Walsh wrote 09/07/2020 at 12:47 point

Be sure to check the Cinnamon file carefully, because it has changed several times with recent updates and probably no longer tracks the one shown here. If you have problems let me know and I'll send you my current version.

  Are you sure? yes | no

Yann Guidon / YGDES wrote 12/27/2016 at 01:02 point

great work !

  Are you sure? yes | no