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

Add ScreenSplit layout #3793

Merged
merged 1 commit into from
Oct 3, 2023
Merged

Add ScreenSplit layout #3793

merged 1 commit into from
Oct 3, 2023

Conversation

elParaguayo
Copy link
Member

@elParaguayo elParaguayo commented Aug 25, 2022

Adds a new layout which allows users to split screen into sub areas and assign a layout to each area, while retaining the ability for fullscreen windows to occupy the whole screen.

This is draft for now as I'm interested in people's thoughts.

This could easily go in qtile-extras if it's not wanted here.

Also, I've not looked at tests yet...

Screenshot (Max on top, Columns on bottom):
20220825_190032

@elParaguayo
Copy link
Member Author

@nazar-pc - copying your question from the discussion here:

Looks interesting and... quite simple? Would it be possible to allow setting a list of layouts for each split or it is expected that multiple ScreenSplits can be defined with a unique combination of layouts in each?

At the moment, each split takes a single layout object. It should be possible to allow a list of layouts if that's useful (but I'd need to block nested ScreenSplit layouts).

@nazar-pc
Copy link
Contributor

Another thing that could be useful is to be able to resize after instantiation like in Column and other layouts.

But even without those changes should be useful and usable, I'll try to test it over the weekend.

@elParaguayo
Copy link
Member Author

Another thing that could be useful is to be able to resize after instantiation like in Column and other layouts.

Unlikely to happen. The logic would get pretty complicated if you had more than 2 splits. The idea is really to break the screen down into smaller, fixed-size screen areas.

@m-col
Copy link
Member

m-col commented Aug 25, 2022

Another difference this has compared to fake_screens is that with fake screens the regions also correspond to groups, allowing you to change groups within one region without changing others. Would this be desirable behaviour?

re fullscreening, we could add a command to fullscreen a window across the entire output layout rather than a single screen (could also useful even when using multiple physical outputs).

And for bars, it appears you can give bars a large negative margin in one direction and they will extend beyond the screen it 'belongs' to. E.g. if you wanted to vertically split a 3840 pixel wide window into two 1920 wide virtual screens, giving the left 'screen' a bar with margin [0, -1920, 0, 0 actually makes it become 1920 pixels wider, running along the top of the full 3840 pixels 😆

@nazar-pc
Copy link
Contributor

I don't think lack of groups is an issue for me personally, the way it is done with static config for one group is perfectly fine for what I have in mind, but moving windows across nested layouts is generally desirable (though may be non-critical for the very initial implementation.

@elParaguayo
Copy link
Member Author

Another difference this has compared to fake_screens is that with fake screens the regions also correspond to groups, allowing you to change groups within one region without changing others. Would this be desirable behaviour?

I had assumed "no" to this question as, otherwise, people should use fake_screens

re fullscreening, we could add a command to fullscreen a window across the entire output layout rather than a single screen (could also useful even when using multiple physical outputs).

A combination of fake_screens and this pretty much negates the need for this layout!

@elParaguayo
Copy link
Member Author

I don't think lack of groups is an issue for me personally, the way it is done with static config for one group is perfectly fine for what I have in mind, but moving windows across nested layouts is generally desirable (though may be non-critical for the very initial implementation.

I haven't implemented moving windows between nested layouts yet.

@elParaguayo
Copy link
Member Author

@nazar-pc I think this is in fairly good shape now. Would be great to get some feedback.

@nazar-pc
Copy link
Contributor

Will do once I am a bit less busy, thank you!

@nazar-pc
Copy link
Contributor

Initial feedback:

  • I really need borders to understand what split is active
  • I wasn't able to move windows across multiple splits, it seems to be stuck and only moves between some two splits instead
  • On every layout I try, I miss master_match from Tile layout to predefine positions for certain apps

I see why resizing is extremely challenging to implement. Maybe one day we'll just have ability to nest any layout in any layout and this will be solved 🙄

@elParaguayo
Copy link
Member Author

elParaguayo commented Aug 31, 2022

Initial feedback:

  • I really need borders to understand what split is active

Pretty hard to do. That's why I added the ScreenSplit widget (but may not have mentioned in this thread...). It tells you the name of the active split.

  • I wasn't able to move windows across multiple splits, it seems to be stuck and only moves between some two splits instead

Interesting. Let me check this.

EDIT: Found the bug. Fixed.

  • On every layout I try, I miss master_match from Tile layout to predefine positions for certain apps

Are you saying you'd like a match rule for each split to put windows in specific splits straightaway? I can look at that.

I see why resizing is extremely challenging to implement. Maybe one day we'll just have ability to nest any layout in any layout and this will be solved roll_eyes

It's good to have dreams 😉

@nazar-pc
Copy link
Contributor

Pretty hard to do. That's why I added the ScreenSplit widget (but may not have mentioned in this thread...). It tells you the name of the active split.

I'm not using any widgets though, I just have tint2 that is hidden 95.5% of the time and no other panels anywhere.

Are you saying you'd like a match rule for each split to put windows in specific splits straightaway?

Exactly. Or else it takes quite a bit of effort to move things around manually every time.

@elParaguayo
Copy link
Member Author

Pretty hard to do. That's why I added the ScreenSplit widget (but may not have mentioned in this thread...). It tells you the name of the active split.

I'm not using any widgets though, I just have tint2 that is hidden 95.5% of the time and no other panels anywhere.

Another idea would be to write a function that's hooked to layout_change and displays a notification (if you've got notifications enabled). The hook is fired every time the active split changes.

Are you saying you'd like a match rule for each split to put windows in specific splits straightaway?

Exactly. Or else it takes quite a bit of effort to move things around manually every time.

Ok. Can add this.

@elParaguayo
Copy link
Member Author

Added matches - see the docs for how to use it (basically just add "matches": [Match(wm_class="vlc")] to the relevant split).

@nazar-pc
Copy link
Contributor

Tested latest version, no issues with moving windows around splits and matches helps a lot too, thanks!

I think borders and having split where mouse is "active" (even if there is no window there, at least for indicating where to spawn new windows) are the two things I have on my mind right now. The "active" part will be less critical if borders are added.

@nazar-pc
Copy link
Contributor

nazar-pc commented Sep 1, 2022

One slightly annoying thing: matches locks windows permanently, I can't move them to other splits even if I try. Can this behavior be only applied to new windows as they are added, but not affecting existing windows? Other layouts like Tile allow moving, but unfortunately re-sort once any other window is opened (which is also annoying).

I'm going to use this layout with Plasm inside of splits (numirias/qtile-plasma#25 is necessary if you're planning to do the same) as the main setup going forward.

@elParaguayo
Copy link
Member Author

Sorry - that was a silly mistake by me. Yes, we can fix it so matches are only applied when windows are opened. Not when we're moving them.

@nazar-pc
Copy link
Contributor

nazar-pc commented Sep 1, 2022

Hm, last commit doesn't seem to match windows anymore

@elParaguayo
Copy link
Member Author

Hm, last commit doesn't seem to match windows anymore

And docs have failed... I must have mucked up the commit!

@nazar-pc
Copy link
Contributor

nazar-pc commented Sep 1, 2022

Please DM me with USDC wallet address, I'd like to send you a few bucks as a thank you 🙂

@elParaguayo
Copy link
Member Author

Please DM me with USDC wallet address, I'd like to send you a few bucks as a thank you slightly_smiling_face

Thanks for the offer but really not needed. I do this for the fun of it, learning new stuff and generally trying to make things better.

@elParaguayo elParaguayo marked this pull request as ready for review September 1, 2022 20:47
@nazar-pc
Copy link
Contributor

nazar-pc commented Sep 7, 2022

Unless I'm doing something wrong I don't think prev/next_split methods actually work. Moving does work, just switching doesn't seem to. I have follow_mouse_focus = True in my layout if that affects it.

@elParaguayo
Copy link
Member Author

Hmm. I suspect follow_mouse_focus will probably affect as the active split will be whichever one has the focussed window.

@elParaguayo
Copy link
Member Author

We've got a big PR to merge first and then we'll look at getting others merged.

I'm also dealing with an issue with our test suite at the moment too.

@nazar-pc
Copy link
Contributor

nazar-pc commented Oct 7, 2022

I found a bug that I think might be related to this PR or to the Plasma layout, not sure yet.

Here is my config:

from libqtile.command import lazy
from libqtile.config import EzDrag, EzKey, EzClick, Screen, Group, Match
from libqtile.layout import Columns, Floating, Matrix, Max, ScreenSplit, Tile
from libqtile import hook
from plasma import Plasma
from xcffib.xproto import StackMode
import subprocess
import os


@hook.subscribe.startup
def dbus_register():
    id = os.environ.get('DESKTOP_AUTOSTART_ID')
    if not id:
        return
    subprocess.Popen([
        'dbus-send',
        '--session',
        '--print-reply',
        '--dest=org.gnome.SessionManager',
        '/org/gnome/SessionManager',
        'org.gnome.SessionManager.RegisterClient',
        'string:qtile',
        'string:'   id
    ])


tint2_instance = None


@hook.subscribe.client_managed
def register_and_bring_to_front_tint2_instance(window):
    global tint2_instance
    if window.name == 'tint2':
        tint2_instance = window
    elif tint2_instance is not None:
        tint2_instance.window.configure(stackmode=StackMode.Above)


@hook.subscribe.client_killed
def unregister_tint2_instance(window):
    global tint2_instance
    if window.name == 'tint2':
        tint2_instance = None


mod = 'mod4'

keymap = {
    'M-<Left>': lazy.layout.left(),
    'M-<Down>': lazy.layout.down(),
    'M-<Up>': lazy.layout.up(),
    'M-<Right>': lazy.layout.right(),
    'M-S-<Left>': [lazy.layout.integrate_left(), lazy.layout.shuffle_left()],
    'M-S-<Down>': [lazy.layout.integrate_down(), lazy.layout.shuffle_down()],
    'M-S-<Up>': [lazy.layout.integrate_up(), lazy.layout.shuffle_up()],
    'M-S-<Right>': [lazy.layout.integrate_right(), lazy.layout.shuffle_right()],
    'M-C-<Left>': [lazy.layout.move_left(), lazy.layout.swap_column_left()],
    'M-C-<Down>': lazy.layout.move_down(),
    'M-C-<Up>': lazy.layout.move_up(),
    'M-C-<Right>': [lazy.layout.move_right(), lazy.layout.swap_column_right()],
    'M-A-<Left>': [lazy.layout.previous_split()],
    'M-A-<Down>': [lazy.layout.next_split()],
    'M-A-<Up>': [lazy.layout.previous_split()],
    'M-A-<Right>': [lazy.layout.next_split()],
    'M-A-S-<Left>': [lazy.layout.move_window_to_previous_split()],
    'M-A-S-<Down>': [lazy.layout.move_window_to_next_split()],
    'M-A-S-<Up>': [lazy.layout.move_window_to_previous_split()],
    'M-A-S-<Right>': [lazy.layout.move_window_to_next_split()],
    'A-S-<Tab>': lazy.layout.previous(),
    'A-<Tab>': lazy.layout.next(),
    'M-h': lazy.layout.mode_horizontal(),
    'M-v': lazy.layout.mode_vertical(),
    'M-S-h': lazy.layout.mode_horizontal_split(),
    'M-S-v': lazy.layout.mode_vertical_split(),
    'M-<End>': [lazy.layout.increase_ratio(), lazy.layout.grow_width(100), lazy.layout.grow_right()],
    'M-<Home>': [lazy.layout.decrease_ratio(), lazy.layout.grow_width(-100), lazy.layout.grow_left()],
    'M-<equal>': lazy.layout.increase_nmaster(),
    'M-<minus>': lazy.layout.decrease_nmaster(),
    'M-<KP_Add>': lazy.layout.increase_nmaster(),
    'M-<KP_Subtract>': lazy.layout.decrease_nmaster(),
    'M-<Page_Up>': [lazy.layout.grow_height(100), lazy.layout.grow_up()],
    'M-<Page_Down>': [lazy.layout.grow_height(-100), lazy.layout.grow_down()],
    'M-<Tab>': lazy.next_layout(),
    'C-A-<Up>': lazy.screen.prev_group(),
    'C-A-<Down>': lazy.screen.next_group(),
    'M-r': lazy.layout.reset_size(),
    'M-w': lazy.window.kill(),
    'C-A-t': lazy.spawn('terminator'),
    'C-A-c': lazy.spawn('gnome-calculator'),
    'C-A-s': lazy.spawn('env XDG_CURRENT_DESKTOP=GNOME gnome-system-monitor'),
    'M-f': lazy.spawn('spacefm'),
    'M-s': lazy.spawn('synapse'),
    '<XF86Tools>': lazy.spawn('clementine'),
    '<Print>': lazy.spawn('gnome-screenshot'),
    'A-<Print>': lazy.spawn('gnome-screenshot --window'),
    'S-<Print>': lazy.spawn('gnome-screenshot --area'),
    'M-l': [lazy.group['0'].toscreen(), lazy.spawn('i3lock -c 000000')],
    'M-S-l': [lazy.group['0'].toscreen(), lazy.spawn('sh -c "i3lock -c 000000; systemctl suspend"')],
    'M-C-r': lazy.restart(),
    'M-a': lazy.spawn('xrandr --auto'),
    'M-c': lazy.spawn('autorandr --load 8k@30'),
    'M-S-c': lazy.spawn('autorandr --load off'),
    'M-<space>': lazy.window.toggle_floating(),
    'M-S-<space>': lazy.window.toggle_fullscreen(),
}

mouse = [
    EzDrag('M-1', lazy.window.set_position_floating(), start=lazy.window.get_position()),
    EzDrag('M-3', lazy.window.set_size_floating(), start=lazy.window.get_size()),
    EzClick('M-2', lazy.window.bring_to_front()),
    EzClick('9', lazy.screen.prev_group()),
    EzClick('8', lazy.screen.next_group()),
]

borders = {
    'border_width': 1,
    'border_width_single': 0,
    'border_normal': '#000000',
    'border_focus': '#318d00',
    # For Plasm layout
    'border_normal_fixed': '#000000',
    'border_focus_fixed': '#318d00',
}

primary_match = [
    Match(wm_class='thunderbird-beta'),
    Match(wm_class='firefox-nightly'),
    Match(wm_class='jetbrains-idea'),
    Match(wm_class='jetbrains-clion'),
    Match(wm_class='clementine'),
    Match(wm_class='smplayer'),
]

email_layout = ScreenSplit(
    name="Email",
    splits=[
        {
            "name": "top",
            "rect": (0, 0, 0.7, 0.4),
            "layout": Plasma(**borders),
        },
        {
            "name": "bottom",
            "rect": (0, 0.4, 0.7, 0.6),
            "layout": Plasma(**borders),
            "matches": primary_match,
        },
        {
            "name": "right",
            "rect": (0.7, 0, 0.3, 1),
            "layout": Plasma(**borders),
            "matches": [Match(wm_class='xpad')],
        },
    ],
    **borders
)
plasma_layout = Plasma(name='Plasma', **borders)
main_layout = ScreenSplit(
    name="Main",
    splits=[
        {
            "name": "left",
            "rect": (0, 0, 0.15, 1),
            "layout": Plasma(
                insert_position=1,
                **borders
            ),
            "matches": [
                Match(wm_class='pavucontrol'),
            ],
        },
        {
            "name": "top",
            "rect": (0.15, 0, 0.6, 0.4),
            "layout": Plasma(
                insert_position=1,
                **borders
            ),
        },
        {
            "name": "bottom",
            "rect": (0.15, 0.4, 0.6, 0.6),
            "layout": Plasma(
                insert_position=1,
                **borders
            ),
            "matches": primary_match,
        },
        {
            "name": "right",
            "rect": (0.75, 0, 0.25, 1),
            "layout": Plasma(
                insert_position=1,
                **borders
            ),
            "matches": [
                Match(wm_class='qbittorrent'),
                Match(wm_class='keepassxc'),
                Match(wm_class='synaptic'),
            ],
        },
    ],
    **borders
)

layouts = [
    main_layout,
    plasma_layout,
    # Tile(
    #     name='Tile',
    #     expand=False,
    #     add_after_last=True,
    #     shift_windows=True,
    #     ratio=0.5,
    #     master_match=primary_match,
    #     **borders
    # ),
    # Columns(
    #     name='Columns',
    #     insert_position=1,
    #     **borders
    # ),
]

floating_layout = Floating(
    # no_reposition_match = Match(wm_class = 'Synapse'),
    **borders
)

groups = [
    Group(
        '1',
        layout='Email',
        layouts=[email_layout, main_layout, plasma_layout],
        matches=[
            Match(wm_class='thunderbird-beta'),
            Match(wm_class='xpad'),
        ],
    ),
    Group(
        '2',
        layout='Main',
        matches=[Match(wm_class='firefox-nightly'), Match(wm_class='qbittorrent'), Match(wm_class='keepassxc')],
    ),
    Group(
        '3',
        layout='Main',
        matches=[Match(wm_class='jetbrains-idea'), Match(wm_class='jetbrains-clion')],
    ),
    Group(
        '4',
        layout='Main',
        matches=[Match(wm_class='clementine'), Match(wm_class='pavucontrol'), Match(wm_class='synaptic')],
    ),
    Group(
        '5',
        layout='Main',
    ),
    Group(
        '6',
        layout='Plasma',
    ),
    Group(
        '7',
        layout='Plasma',
    ),
    Group(
        '8',
        layout='Plasma',
    ),
    Group(
        '9',
        layout='Plasma',
    ),
    Group(
        '0',
        layout='Plasma',
    ),
]

for i in groups:
    keymap['M-'   i.name] = [
        lazy.group[i.name].toscreen()
    ]
    keymap['M-S-'   i.name] = [
        lazy.window.togroup(i.name),
        lazy.group[i.name].toscreen()
    ]

keys = []
for k, v in keymap.items():
    if type(v) is list:
        keys.append(EzKey(k, *v))
    else:
        keys.append(EzKey(k, v))

screens = [Screen()]

follow_mouse_focus = True
bring_front_click = False
cursor_warp = False
auto_fullscreen = True
focus_on_window_activation = 'smart'

wmname = 'LG3D'

I open Firefox Nightly, it gets opened in the second group, bottom part (the biggest), then I open private window of the same Nightly, move regular window into another split, which makes private window occupy the whole split and close private window.

I end up in a situation where:

  1. I can no longer move Firefox window between splits, it doesn't get activated
  2. 50% of Plasma layout in the bottom split is occupied by "nothing" like if there is an app there, but there isn't (even though private window before that occupied 100% of the split)

I hit it a few times before, but only now I figured out how to reproduce it reliably.
Might be a bug in Plasma layout, but may also be something related to ScreeSplit.

For Plasma layout to work within ScreenSplit numirias/qtile-plasma#25 is needed.

@elParaguayo
Copy link
Member Author

Can you recreate this without using the Plasma layout?

@github-actions
Copy link

github-actions bot commented Jul 6, 2023

This PR is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 30 days.

@nazar-pc
Copy link
Contributor

nazar-pc commented Jul 6, 2023

Please don't close, I'm using it and looking forward for it to be merged 🙏

@github-actions github-actions bot closed this Aug 5, 2023
@nazar-pc
Copy link
Contributor

nazar-pc commented Aug 5, 2023

Can someone reopen this? I'd hate it to be forgotten, this is an extremely useful layout!

@elParaguayo elParaguayo reopened this Aug 5, 2023
@elParaguayo
Copy link
Member Author

I wonder if i need to put this in qtile-extras...

@Nebucatnetzer
Copy link

Nebucatnetzer commented Sep 7, 2023

It sounds quite interesting for large monitors.
I'm currently looking at fake_screens because I would like to have one quarter or my monitor use TreeTab because applications like Remmina open new windows and mess up the layout and I still would like to have the main window at hand.

@nazar-pc
Copy link
Contributor

I wonder if i need to put this in qtile-extras...

Regardless of where it goes, I just hope it gets merged and maintained. I don't think Qtile will be usable for me on 8k screen without this.

@elParaguayo
Copy link
Member Author

I think we'd like to include this in the main repo. I'll put this back on my to do list so I can tidy up the code and get the tests passing.

@jwijenbergh
Copy link
Contributor

Yes 1 this sounds like a must have for ultrawide monitor users

@m-col
Copy link
Member

m-col commented Sep 25, 2023

1

@elParaguayo
Copy link
Member Author

OK. I've rebased but I suspect there'll be some test failures for me to work on ;)

@elParaguayo elParaguayo force-pushed the screensplit branch 2 times, most recently from 64665cd to 3c68ff7 Compare September 30, 2023 12:05
@elParaguayo
Copy link
Member Author

Everything's passing now so squashed into a single commit.

Adds a new layout which allows users to split screen into sub areas
and assign a layout to each area, while retaining the ability
for fullscreen windows to occupy the whole screen.
@m-col
Copy link
Member

m-col commented Oct 3, 2023

Tests are pretty happy ;)

@elParaguayo elParaguayo merged commit 6c4b5ef into qtile:master Oct 3, 2023
18 checks passed
@elParaguayo elParaguayo deleted the screensplit branch October 3, 2023 19:44
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

Successfully merging this pull request may close these issues.

5 participants