3

I'm coming from Windows 10, although I've started (and accepted) the way Ubuntu works, their are still a few things that I love about Windows 10, the feature in question here is the way If I snap one windows to the extreme right side of display it takes up the right half portion of the display (which is also similar in ubuntu) but another app in background takes the left half of display and doesn't need to manually do that.

Screenshot

In this picture, if I snap the Browser windows to the right, Nautilus window wouldn't affect by the action, But I want it (nautilus window) to snap to left

Details

  • Ubuntu 17.04
  • Gnome flavour

Update

result of the fillscreen.py script

1st result

in first try fillscreen notification came (I couldn't capture that) and it moved the 2nd window (window in the right) in a box, and 1st window wasn't affected at all.

2nd result

in second try, this offset was present, but it did work (and it mostly works)

  • 1
    Hi Sumeet, if you run this one askubuntu.com/a/709787/72216 with the arguments 2 1 : python3 /path/to/script.py 2 1 it will do exactly what you want. Please let me know if you manage or not. – Jacob Vlijm Apr 22 '17 at 7:46
  • @JacobVlijm that did solve my problem, but i was looking something like Windows 10 implementation, where other window just snaps to the opposite side – Sumeet Deshmukh Apr 22 '17 at 14:23
  • 1
    It would be scriptable thoug, but it might be too demanding. I might do an attempt... will let you know if it is a usable option. – Jacob Vlijm Apr 22 '17 at 16:42
  • 1
    Hi Sumeet, I am pretty sure "and 1st window wasn't affected at all" is cuased by the fact that the second window was minimized. Mind: The script below will do exactly as you describe on the two youngest windows, that is: the two windows that were last created. Please make sure you run it on the latest two windows. The offset is something that happens occasionally here, and is the result of a kind of buggy wmctrl i.c.w. your window manager. I am not sure I can fix it. It is not the command that is wrong, but it is occasionally executed in a buggy manner. – Jacob Vlijm Apr 24 '17 at 9:01
  • 1
    Yeah, I'll probably keep working on it. Will update of course if I can fix occasional offset. – Jacob Vlijm Apr 24 '17 at 9:49
2

Important note!

The script below will do exactly as you describe on the two youngest windows, that is: the two windows that were last created.

The script, behavior

  • The script acts on dragging one of the two "newest" windows to one of the two areas on the screen, as shown in the image.

    enter image description here

    The area is deliberately not tight into the corner, to make sure it does not interfere with "normal"window snapping.

  • If the window is dragged on to either one of the areas, the script waits 0.15 second to see if the mouse is still in the same position, to make sure not to act if the user was "on his way" to the corner of the screen for normal window snapping.

  • Subsequently, the dragged window is snapped into the half of the screen the area is on, the second window is snapped to the opposite side of the screen

    1. drag the window to the area

    enter image description here

    2. the window snaps, the other one snaps to the opposite site

    enter image description here

  • finally, as a confirmation, a notification shows during three seconds:

    enter image description here

    See the script in action

The script & setup

The setup involves two items:

  • the script:

    #!/usr/bin/env python3
    import sys
    import os
    import subprocess
    import time
    from operator import itemgetter
    from itertools import groupby
    import math
    
    #--- set your preferences below: padding between windows, margin(s)
    cols = 2; rows = 1; padding = 20; left_margin = 0; top_margin = 30
    #---
    
    fpath = os.path.dirname(os.path.abspath(__file__))
    n_wins = cols*rows
    
    
    def get_spot(pos):
        # get the resolution
        scrdata = get("xrandr").split(); resindex = scrdata.index("connected")+2
        res = [int(n) for n in scrdata[resindex].split("+")[0].split("x")]
        # list the corners, could be more elegant no doubt
        corners = [[0, res[1]], [res[0], res[1]]]
        diff = [int(math.sqrt(sum([(c[i]-pos[i])**2 for i, n in enumerate(res)])))\
                for c in corners]
        return diff
    
    def get(cmd):
        try:
            return subprocess.check_output(cmd).decode("utf-8")
        except subprocess.CalledProcessError:
            pass
    
    def get_res():
        xr = get("xrandr").split(); pos = xr.index("current")
        return [int(xr[pos+1]), int(xr[pos+3].replace(",", "") )]
    
    def get_pos():
        return [int(s.split(":")[1]) for s in get(["xdotool", "getmouselocation"]).split()[:2]]
    
    
    def check_window(w_id):
        w_type = get(["xprop", "-id", w_id])
        if " _NET_WM_WINDOW_TYPE_NORMAL" in w_type:
            return True
        else:
            return False
    
    def confirm():
        val = False
        mouseloc = get_spot(get_pos())
        match = [mouseloc.index(n) for n in mouseloc if 50 < n < 400]
        if match:
            time.sleep(0.15)
            val = True if get_spot(get_pos()) == mouseloc else False
        return val, match
    
    def arrange_wins(active, side):
        # get resolution
        res = get_res()
        # define (calculate) the area to divide
        area_h = res[0] - left_margin; area_v = res[1] - top_margin
        # create a list of calculated coordinates
        x_coords = [int(left_margin+area_h/cols*n) for n in range(cols)]
        y_coords = [int(top_margin+area_v/rows*n) for n in range(rows)]
        coords = sum([[(cx, cy) for cx in x_coords] for cy in y_coords], [])
        # calculate the corresponding window size, given the padding, margins, columns and rows
        w_size = [str(int(area_h/cols - padding)), str(int(area_v/rows - padding))]
        # find windows of the application, identified by their pid
        active = hex(int(get(["xdotool", "getactivewindow"])))
        active = active[:2]+(10-len(active))*"0"+active[2:]
        wlist = [w.split()[0] for w in get(["wmctrl", "-l"]).splitlines()]
        w_list = [w for w in wlist if check_window(w) == True][-n_wins:]
        try:
            w_list = w_list[::-1] if w_list.index(active) != side else w_list
        except ValueError:
            pass
        else: 
            print(w_list)
            # remove possibly maximization, move the windows
            for n, w in enumerate(w_list):
                data = (",").join([str(item) for item in coords[n]])+","+(",").join(w_size)
                cmd1 = "wmctrl -ir "+w+" -b remove,maximized_horz"
                cmd2 = "wmctrl -ir "+w+" -b remove,maximized_vert"
                cmd3 = "wmctrl -ir "+w+" -e 0,"+data
                for cmd in [cmd1, cmd2, cmd3]:
                    subprocess.Popen(["/bin/bash", "-c", cmd])
    
    wins1 = []
    
    while True:
        time.sleep(0.5)
        windata = get(["wmctrl", "-lG"])
        if windata:
            wins2 = [[l[0], l[2]] for l in [
                ln.split() for ln in windata.splitlines()]
                       ]
            # combined window locations old/new, grouped to see if moved
            winlocs = sorted(wins1 + wins2, key = itemgetter(0))
            test = [[item, [item[1] for item in list(occ)]] \
                    for item, occ in groupby(winlocs, itemgetter(0))]
            for item in test:
                # old loc, new loc of window
                locs = item[1]
                # window moves?
                if locs.count(locs[0]) != len(locs):
                    args = confirm()
                    if args[0]:
                        arrange_wins(item[0], args[1][0])
                        subprocess.Popen([
                            "notify-send", "-i", os.path.join(
                                fpath, "left.png"), "Fill screen"
                            ])
                        time.sleep(3)
                        subprocess.Popen(["pkill", "notify-osd"])
            wins1 = wins2
    
  • an icon to show in the notification

    enter image description here

Setup

  1. Install both xdotooland wmctrl
  2. Copy the script into an empty file, save it as fillscreen.py in a dedicated folder somewhere.
  3. Right- click on the icon above, save it as (exactly) left.png in one and the same folder as the script.
  4. Now open a terminal, run the command:

    python3 /path/to/fillscreen.py
    

    Note that this terminal window is one of the two windows the script will snap. Draf the terminal to either one of the areas on the left or right. The two most recent windows should snap.

  5. If all works fine, add the script to startup applications: Dash > Startup Applications > Add. Add the command:

    /bin/bash -c "sleep 10 && python3 /path/to/fillscreen.py"
    

Note

Due to the fact that the script only acts on window movement, and subsequently all further actions are dependent on the situation, the script is very low on juice. Much lower then I expected it to be when I started working on it.

  • everything worked except the last point, it says text ended before matching quote was found for " i tried to add quote after "sleep 10" or "sleep" 10, it got saved but the script isn't working, maybe i did it wrong. another point is when it does work (by manually) starting it in terminal, their is a strange offset to windows – Sumeet Deshmukh Apr 24 '17 at 2:54
  • Hi @SumeetDeshmukh 1. is a typo, 2. can be tweaked a bit, but applcations/windowmanagers handle windowpositions slightly different. Could you post a screenshot somewhere? – Jacob Vlijm Apr 24 '17 at 5:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.