Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions book/src/configuration/misc.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,20 @@ The value should be a keysym name (see the
[xkbcommon keysym list](https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h)
with the `XKB_KEY_` prefix removed).

## Flatten Tree

When a window is removed from a container (e.g. moved to another workspace or
floated), the container may end up with only one child. By default the
container is kept, resulting in redundant nesting.

Setting `flatten-tree` to `true` automatically collapses
such containers by replacing them with their only remaining child, keeping the
window tree flat.

```toml
flatten-tree = true
```

## Middle-Click Paste

Controls whether middle-clicking pastes the primary selection. Changing this
Expand Down
4 changes: 4 additions & 0 deletions jay-config/src/_private/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1101,6 +1101,10 @@ impl ConfigClient {
self.send(&ClientMessage::SetMiddleClickPasteEnabled { enabled });
}

pub fn set_flatten_tree(&self, enabled: bool) {
self.send(&ClientMessage::SetFlattenTree { enabled });
}

pub fn open_control_center(&self) {
self.send(&ClientMessage::OpenControlCenter);
}
Expand Down
3 changes: 3 additions & 0 deletions jay-config/src/_private/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,9 @@ pub enum ClientMessage<'a> {
SetMiddleClickPasteEnabled {
enabled: bool,
},
SetFlattenTree {
enabled: bool,
},
Comment on lines +759 to +761

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New IPC messages must be added at the end to not break existing configs.

SeatCreateMark {
seat: Seat,
kc: Option<u32>,
Expand Down
10 changes: 10 additions & 0 deletions jay-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,16 @@ pub fn set_middle_click_paste_enabled(enabled: bool) {
get!().set_middle_click_paste_enabled(enabled);
}

/// Enables or disables tree flattening.
///
/// When enabled, a container with only one child is automatically collapsed
/// by replacing it with its child, keeping the tree tidy.
///
/// The default is `false`.
pub fn set_flatten_tree(enabled: bool) {
get!().set_flatten_tree(enabled);
}

/// Opens the control center.
pub fn open_control_center() {
get!().open_control_center();
Expand Down
1 change: 1 addition & 0 deletions src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ fn start_compositor2(
color_management_enabled: Cell::new(false),
color_manager,
float_above_fullscreen: Cell::new(false),
flatten_tree: Cell::new(false),
icons: Default::default(),
show_pin_icon: Cell::new(false),
cl_matcher_manager: ClMatcherManager::new(&crit_ids),
Expand Down
5 changes: 5 additions & 0 deletions src/config/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2595,6 +2595,10 @@ impl ConfigProxyHandler {
self.state.set_primary_selection_enabled(enabled);
}

fn handle_set_flatten_tree(&self, enabled: bool) {
self.state.set_flatten_tree(enabled);
}

fn handle_seat_create_mark(&self, seat: Seat, kc: Option<u32>) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
if let Some(kc) = kc {
Expand Down Expand Up @@ -3625,6 +3629,7 @@ impl ConfigProxyHandler {
ClientMessage::SetMiddleClickPasteEnabled { enabled } => {
self.handle_set_middle_click_paste_enabled(enabled)
}
ClientMessage::SetFlattenTree { enabled } => self.handle_set_flatten_tree(enabled),
ClientMessage::SetWorkspaceDisplayOrder { order } => {
self.handle_set_workspace_display_order(order)
}
Expand Down
5 changes: 5 additions & 0 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ pub struct State {
pub color_management_enabled: Cell<bool>,
pub color_manager: Rc<ColorManager>,
pub float_above_fullscreen: Cell<bool>,
pub flatten_tree: Cell<bool>,
pub icons: Icons,
pub show_pin_icon: Cell<bool>,
pub cl_matcher_manager: ClMatcherManager,
Expand Down Expand Up @@ -2096,6 +2097,10 @@ impl State {
self.root.update_visible(self);
}

pub fn set_flatten_tree(&self, v: bool) {
self.flatten_tree.set(v);
}

pub fn reset_sizes(&self) {
self.theme.sizes.reset();
self.spaces_changed();
Expand Down
22 changes: 22 additions & 0 deletions src/tree/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ impl ContainerNode {
slf
}

pub fn num_children(&self) -> usize {
self.num_children.get()
}

pub fn prepend_child(self: &Rc<Self>, new: Rc<dyn ToplevelNode>) {
if let Some(child) = self.children.first() {
self.add_child_before_(&child, new);
Expand Down Expand Up @@ -2114,6 +2118,24 @@ impl ContainingNode for ContainerNode {
self.tl_destroy();
return;
}
if num_children == 1 && self.state.flatten_tree.get() {
if let Some(parent) = self.toplevel_data.parent.get()
&& !self.toplevel_data.is_fullscreen.get()
{
let remaining = {
let cn = self.child_nodes.borrow();
let (_, v) = cn.iter().next().unwrap();
v.node.clone()
};
if parent.cnode_accepts_child(&*remaining) {
parent.cnode_replace_child(self.deref(), remaining);
self.toplevel_data.parent.take();
self.child_nodes.borrow_mut().clear();
self.tl_destroy();
return;
}
}
}
self.update_content_size();
let rem = 1.0 - node.factor.get();
let mut sum = 0.0;
Expand Down
7 changes: 7 additions & 0 deletions src/tree/toplevel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1067,6 +1067,13 @@ pub fn toplevel_create_split(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, axis:
Some(pn) => pn,
_ => return,
};
if let Some(container) = pn.clone().node_into_container()
&& container.num_children() == 1
&& state.flatten_tree.get()
{
container.set_split(axis);
return;
}
if let Some(pn) = pn.node_into_containing_node() {
let cn = ContainerNode::new(state, &ws, tl.clone(), axis);
pn.cnode_replace_child(&*tl, cn);
Expand Down
1 change: 1 addition & 0 deletions toml-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ pub struct Config {
pub use_hardware_cursor: Option<bool>,
pub show_bar: Option<bool>,
pub show_titles: Option<bool>,
pub flatten_tree: Option<bool>,
pub focus_history: Option<FocusHistory>,
pub middle_click_paste: Option<bool>,
pub input_modes: AHashMap<String, InputMode>,
Expand Down
9 changes: 7 additions & 2 deletions toml-config/src/config/parsers/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl Parser for ConfigParser<'_> {
clean_logs_older_than_val,
mouse_follows_focus,
),
(session_management_val, workspaces_val),
(session_management_val, workspaces_val, flatten_tree),
) = ext.extract((
(
opt(val("keymap")),
Expand Down Expand Up @@ -220,7 +220,11 @@ impl Parser for ConfigParser<'_> {
opt(val("clean-logs-older-than")),
recover(opt(bol("unstable-mouse-follows-focus"))),
),
(opt(val("session-management")), opt(val("workspaces"))),
(
opt(val("session-management")),
opt(val("workspaces")),
recover(opt(bol("flatten-tree"))),
),
))?;
let mut keymap = None;
if let Some(value) = keymap_val {
Expand Down Expand Up @@ -633,6 +637,7 @@ impl Parser for ConfigParser<'_> {
use_hardware_cursor: use_hardware_cursor.despan(),
show_bar: show_bar.despan(),
show_titles: show_titles.despan(),
flatten_tree: flatten_tree.despan(),
focus_history,
middle_click_paste: middle_click_paste.despan(),
input_modes,
Expand Down
5 changes: 4 additions & 1 deletion toml-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use {
logging::{clean_logs_older_than, set_log_level},
on_devices_enumerated, on_idle, on_unload, open_control_center, quit, reload,
set_color_management_enabled, set_default_workspace_capture, set_explicit_sync_enabled,
set_float_above_fullscreen, set_idle, set_idle_grace_period,
set_flatten_tree, set_float_above_fullscreen, set_idle, set_idle_grace_period,
set_middle_click_paste_enabled, set_session_management_enabled, set_show_bar,
set_show_float_pin_icon, set_show_titles, set_ui_drag_enabled, set_ui_drag_threshold,
status::{set_i3bar_separator, set_status, set_status_command, unset_status_command},
Expand Down Expand Up @@ -1677,6 +1677,9 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
if let Some(v) = config.show_titles {
set_show_titles(v);
}
if let Some(v) = config.flatten_tree {
set_flatten_tree(v);
}
if let Some(v) = config.theme.bar_position {
set_bar_position(v);
}
Expand Down
4 changes: 4 additions & 0 deletions toml-spec/spec/spec.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,10 @@
"type": "boolean",
"description": "Configures whether title bars on windows are shown.\n\nThe default is `true`.\n"
},
"flatten-tree": {
"type": "boolean",
"description": "Configures whether containers with only a single child are automatically\ncollapsed by replacing them with their child, keeping the window tree\nflat.\n\nWhen a window is moved away from a container and the container ends up\nwith a single remaining window, enabling this option removes the\ncontainer and places the remaining window directly in the parent\ncontainer.\n\nThe default is `false`.\n"
},
"focus-history": {
"description": "Configures the focus-history settings.\n\n- Example:\n\n ```toml\n [focus-history]\n only-visible: true\n same-workspace: true\n ```\n",
"$ref": "#/$defs/FocusHistory"
Expand Down
15 changes: 15 additions & 0 deletions toml-spec/spec/spec.generated.md
Original file line number Diff line number Diff line change
Expand Up @@ -2461,6 +2461,21 @@ The table has the following fields:

The value of this field should be a boolean.

- `flatten-tree` (optional):

Configures whether containers with only a single child are automatically
collapsed by replacing them with their child, keeping the window tree
flat.

When a window is moved away from a container and the container ends up
with a single remaining window, enabling this option removes the
container and places the remaining window directly in the parent
container.

The default is `false`.

The value of this field should be a boolean.

- `focus-history` (optional):

Configures the focus-history settings.
Expand Down
14 changes: 14 additions & 0 deletions toml-spec/spec/spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3249,6 +3249,20 @@ Config:
Configures whether title bars on windows are shown.

The default is `true`.
flatten-tree:
kind: boolean
required: false
description: |
Configures whether containers with only a single child are automatically
collapsed by replacing them with their child, keeping the window tree
flat.

When a window is moved away from a container and the container ends up
with a single remaining window, enabling this option removes the
container and places the remaining window directly in the parent
container.

The default is `false`.
focus-history:
ref: FocusHistory
required: false
Expand Down
Loading