From 8bfca48991ce85f065eea4d2456908eebb9529bc Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Mon, 13 Apr 2020 21:00:18 +0200 Subject: [PATCH] Implement the focus stack --- src/command.zig | 140 ++++++++++++++++++------------------------ src/cursor.zig | 2 +- src/input_manager.zig | 9 +++ src/render.zig | 2 +- src/root.zig | 29 +++------ src/seat.zig | 96 +++++++++++++++++++++++++++++ src/view.zig | 55 ++++------------- 7 files changed, 186 insertions(+), 147 deletions(-) diff --git a/src/command.zig b/src/command.zig index 28023ce..347a1d7 100644 --- a/src/command.zig +++ b/src/command.zig @@ -21,62 +21,46 @@ pub fn exitCompositor(seat: *Seat, arg: Arg) void { c.wl_display_terminate(seat.input_manager.server.wl_display); } +/// Focus either the next or the previous visible view, depending on the bool +/// passed. +fn focusNextPrevView(seat: *Seat, next: bool) void { + const output = seat.input_manager.server.root.focusedOutput(); + if (seat.focused_view) |current_focus| { + // If there is a currently focused view, focus the next visible view in the stack. + const focused_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); + var it = if (next) + ViewStack(View).iterator(focused_node, output.current_focused_tags) + else + ViewStack(View).reverseIterator(focused_node, output.current_focused_tags); + + // Skip past the focused node + _ = it.next(); + // Focus the next visible node if there is one + if (it.next()) |node| { + seat.focus(&node.view); + return; + } + } + + // There is either no currently focused view or the last visible view in the + // stack is focused and we need to wrap. + var it = if (next) + ViewStack(View).iterator(output.views.first, output.current_focused_tags) + else + ViewStack(View).reverseIterator(output.views.last, output.current_focused_tags); + seat.focus(if (it.next()) |node| &node.view else null); +} + /// Focus the next visible view in the stack, wrapping if needed. Does /// nothing if there is only one view in the stack. pub fn focusNextView(seat: *Seat, arg: Arg) void { - // FIXME: this need to be rewritten the next commit adding a focus stack - //const output = self.focusedOutput(); - //if (self.focused_view) |current_focus| { - // // If there is a currently focused view, focus the next visible view in the stack. - // const current_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); - // var it = ViewStack(View).iterator(current_node, output.current_focused_tags); - // // Skip past the current node - // _ = it.next(); - // // Focus the next visible node if there is one - // if (it.next()) |node| { - // node.view.focus(node.view.wlr_xdg_surface.surface); - // return; - // } - //} - - //// There is either no currently focused view or the last visible view in the - //// stack is focused and we need to wrap. - //var it = ViewStack(View).iterator(output.views.first, output.current_focused_tags); - //if (it.next()) |node| { - // node.view.focus(node.view.wlr_xdg_surface.surface); - //} else { - // // Otherwise clear the focus since there are no visible views - // self.clearFocus(); - //} + focusNextPrevView(seat, true); } /// Focus the previous view in the stack, wrapping if needed. Does nothing /// if there is only one view in the stack. pub fn focusPrevView(seat: *Seat, arg: Arg) void { - // FIXME: this need to be rewritten the next commit adding a focus stack - //const output = self.focusedOutput(); - //if (self.focused_view) |current_focus| { - // // If there is a currently focused view, focus the previous visible view in the stack. - // const current_node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); - // var it = ViewStack(View).reverseIterator(current_node, output.current_focused_tags); - // // Skip past the current node - // _ = it.next(); - // // Focus the previous visible node if there is one - // if (it.next()) |node| { - // node.view.focus(node.view.wlr_xdg_surface.surface); - // return; - // } - //} - - //// There is either no currently focused view or the first visible view in the - //// stack is focused and we need to wrap. - //var it = ViewStack(View).reverseIterator(output.views.last, output.current_focused_tags); - //if (it.next()) |node| { - // node.view.focus(node.view.wlr_xdg_surface.surface); - //} else { - // // Otherwise clear the focus since there are no visible views - // self.clearFocus(); - //} + focusNextPrevView(seat, false); } /// Modify the number of master views @@ -109,16 +93,16 @@ pub fn modifyMasterFactor(seat: *Seat, arg: Arg) void { /// Bump the focused view to the top of the stack. /// TODO: if the top of the stack is focused, bump the next visible view. pub fn zoom(seat: *Seat, arg: Arg) void { - // FIXME rewrite after next commit adding focus stack - //if (server.root.focused_view) |current_focus| { - // const output = server.root.focusedOutput(); - // const node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); - // if (node != output.views.first) { - // output.views.remove(node); - // output.views.push(node); - // server.root.arrange(); - // } - //} + if (seat.focused_view) |current_focus| { + const root = &seat.input_manager.server.root; + const output = root.focusedOutput(); + const node = @fieldParentPtr(ViewStack(View).Node, "view", current_focus); + if (node != output.views.first) { + output.views.remove(node); + output.views.push(node); + root.arrange(); + } + } } /// Switch focus to the passed tags. @@ -144,31 +128,28 @@ pub fn toggleTags(seat: *Seat, arg: Arg) void { /// Set the tags of the focused view. pub fn setFocusedViewTags(seat: *Seat, arg: Arg) void { - // FIXME - //const tags = arg.uint; - //if (server.root.focused_view) |view| { - // if (view.current_tags != tags) { - // view.pending_tags = tags; - // server.root.arrange(); - // } - //} + const tags = arg.uint; + if (seat.focused_view) |view| { + if (view.current_tags != tags) { + view.pending_tags = tags; + seat.input_manager.server.root.arrange(); + } + } } /// Toggle the passed tags of the focused view pub fn toggleFocusedViewTags(seat: *Seat, arg: Arg) void { - // FIXME: rewrite afet next commit adding focus stack - //const tags = arg.uint; - //if (server.root.focused_view) |view| { - // const new_tags = view.current_tags ^ tags; - // if (new_tags != 0) { - // view.pending_tags = new_tags; - // server.root.arrange(); - // } - //} + const tags = arg.uint; + if (seat.focused_view) |view| { + const new_tags = view.current_tags ^ tags; + if (new_tags != 0) { + view.pending_tags = new_tags; + seat.input_manager.server.root.arrange(); + } + } } /// Spawn a program. -/// TODO: make this take a program as a paramter and spawn that pub fn spawn(seat: *Seat, arg: Arg) void { const cmd = arg.str; @@ -185,8 +166,7 @@ pub fn spawn(seat: *Seat, arg: Arg) void { /// Close the focused view, if any. pub fn close(seat: *Seat, arg: Arg) void { - // FIXME: see above - //if (server.root.focused_view) |view| { - // view.close(); - //} + if (seat.focused_view) |view| { + view.close(); + } } diff --git a/src/cursor.zig b/src/cursor.zig index af41c77..839276a 100644 --- a/src/cursor.zig +++ b/src/cursor.zig @@ -273,7 +273,7 @@ pub const Cursor = struct { } else { // Focus that client if the button was _pressed_ if (view) |v| { - v.focus(surface.?); + cursor.seat.focus(v); } } } diff --git a/src/input_manager.zig b/src/input_manager.zig index 885680c..30c06bc 100644 --- a/src/input_manager.zig +++ b/src/input_manager.zig @@ -30,6 +30,15 @@ pub const InputManager = struct { c.wl_signal_add(&self.server.wlr_backend.events.new_input, &self.listen_new_input); } + /// Must be called whenever a view is unmapped. + pub fn handleViewUnmap(self: Self, view: *View) void { + var it = self.seats.first; + while (it) |node| : (it = node.next) { + const seat = &node.data; + seat.handleViewUnmap(view); + } + } + /// This event is raised by the backend when a new input device becomes available. fn handleNewInput(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { const input_manager = @fieldParentPtr(InputManager, "listen_new_input", listener.?); diff --git a/src/render.zig b/src/render.zig index 074228c..bd08400 100644 --- a/src/render.zig +++ b/src/render.zig @@ -262,7 +262,7 @@ fn renderSurface(_surface: ?*c.wlr_surface, sx: c_int, sy: c_int, data: ?*c_void fn renderBorders(output: Output, view: *View, now: *c.struct_timespec, ox: f64, oy: f64) void { var border: c.wlr_box = undefined; - const color = if (output.root.focused_view == view) + const color = if (view.wlr_xdg_surface.unnamed_163.toplevel.*.current.activated) [_]f32{ 0.57647059, 0.63137255, 0.63137255, 1.0 } // Solarized base1 else [_]f32{ 0.34509804, 0.43137255, 0.45882353, 1.0 }; // Solarized base01 diff --git a/src/root.zig b/src/root.zig index 38c09f6..0c470d2 100644 --- a/src/root.zig +++ b/src/root.zig @@ -19,10 +19,6 @@ pub const Root = struct { wlr_output_layout: *c.wlr_output_layout, outputs: std.TailQueue(Output), - /// The view that has seat focus, if any. - /// TODO: move this to Seat - focused_view: ?*View, - /// Number of pending configures sent in the current transaction. /// A value of 0 means there is no current transaction. pending_configures: u32, @@ -41,8 +37,6 @@ pub const Root = struct { self.outputs = std.TailQueue(Output).init(); - self.focused_view = null; - self.pending_configures = 0; self.transaction_timer = null; @@ -188,8 +182,8 @@ pub const Root = struct { // Iterate over all views of all outputs var output_it = self.outputs.first; - while (output_it) |node| : (output_it = node.next) { - const output = &node.data; + while (output_it) |output_node| : (output_it = output_node.next) { + const output = &output_node.data; // If there were pending focused tags, make them the current focus if (output.pending_focused_tags) |tags| { @@ -199,9 +193,6 @@ pub const Root = struct { ); output.current_focused_tags = tags; output.pending_focused_tags = null; - - self.focused_view = null; - Log.Error.log("FIXME: this needs to iterate over all seats and focus(null)", .{}); } var view_it = ViewStack(View).iterator(output.views.first, 0xFFFFFFFF); @@ -218,20 +209,16 @@ pub const Root = struct { if (view.pending_tags) |tags| { view.current_tags = tags; view.pending_tags = null; - - // If the pending tags caused the currently focused view to no - // longer be visible, focus the next view. - if (self.focused_view) |focus| { - if (focus == view and - view.current_tags & output.current_focused_tags == 0) - { - Log.Error.log("FIXME: this needs to iterate over all seats and focus(null)", .{}); - } - } } view.dropStashedBuffer(); } } + + // Iterate over all seats and update focus + var it = self.server.input_manager.seats.first; + while (it) |seat_node| : (it = seat_node.next) { + seat_node.data.focus(null); + } } }; diff --git a/src/seat.zig b/src/seat.zig index 646945c..34cdbfa 100644 --- a/src/seat.zig +++ b/src/seat.zig @@ -2,8 +2,11 @@ const std = @import("std"); const c = @import("c.zig"); const Cursor = @import("cursor.zig").Cursor; +const Log = @import("log.zig").Log; const InputManager = @import("input_manager.zig").InputManager; const Keyboard = @import("keyboard.zig").Keyboard; +const View = @import("view.zig").View; +const ViewStack = @import("view_stack.zig").ViewStack; pub const Seat = struct { const Self = @This(); @@ -17,6 +20,13 @@ pub const Seat = struct { /// Mulitple keyboards are handled separately keyboards: std.TailQueue(Keyboard), + /// Currently focused view if any + focused_view: ?*View, + + /// Stack of views in most recently focused order + /// If there is a currently focused view, it is on top. + focus_stack: ViewStack(*View), + pub fn init(self: *Self, input_manager: *InputManager, name: []const u8) !void { self.input_manager = input_manager; @@ -28,12 +38,98 @@ pub const Seat = struct { errdefer self.cursor.destroy(); self.keyboards = std.TailQueue(Keyboard).init(); + + self.focused_view = null; + + self.focus_stack.init(); } pub fn destroy(self: Self) void { self.cursor.destroy(); } + /// Set the current focus. If a visible view is passed it will be focused. + /// If null is passed, the first visible view in the focus stack will be focused. + pub fn focus(self: *Self, _view: ?*View) void { + var view = _view; + + // If view is null or not currently visible + if (if (view) |v| v.current_tags & v.output.current_focused_tags == 0 else true) { + // Set view to the first currently visible view in the focus stack if any + view = if (ViewStack(*View).iterator( + self.focus_stack.first, + self.input_manager.server.root.focusedOutput().current_focused_tags, + ).next()) |node| node.view else null; + } + + if (self.focused_view) |current_focus| { + // Don't refocus the currently focused view + if (if (view) |v| current_focus == v else false) { + return; + } + // Deactivate the currently focused view + current_focus.setActivated(false); + } + + if (view) |to_focus| { + // Find or allocate a new node in the focus stack for the target view + var it = self.focus_stack.first; + while (it) |node| : (it = node.next) { + // If the view is found, move it to the top of the stack + if (node.view == to_focus) { + const new_focus_node = self.focus_stack.remove(node); + self.focus_stack.push(node); + break; + } + } else { + // The view is not in the stack, so allocate a new node and prepend it + const new_focus_node = self.input_manager.server.allocator.create( + ViewStack(*View).Node, + ) catch unreachable; + new_focus_node.view = to_focus; + self.focus_stack.push(new_focus_node); + } + + // The target view is now at the top of the focus stack, so activate it + to_focus.setActivated(true); + + // Tell the seat to have the keyboard enter this surface. wlroots will keep + // track of this and automatically send key events to the appropriate + // clients without additional work on your part. + const keyboard: *c.wlr_keyboard = c.wlr_seat_get_keyboard(self.wlr_seat); + c.wlr_seat_keyboard_notify_enter( + self.wlr_seat, + to_focus.wlr_xdg_surface.surface, + &keyboard.keycodes, + keyboard.num_keycodes, + &keyboard.modifiers, + ); + } + + self.focused_view = view; + } + + /// Handle the unmapping of a view, removing it from the focus stack and + /// setting the focus if needed. + pub fn handleViewUnmap(self: *Self, view: *View) void { + // Remove the node from the focus stack and destroy it. + var it = self.focus_stack.first; + while (it) |node| : (it = node.next) { + if (node.view == view) { + self.focus_stack.remove(node); + self.input_manager.server.allocator.destroy(node); + break; + } + } + + // If the unmapped view is focused, choose a new focus + if (self.focused_view) |current_focus| { + if (current_focus == view) { + self.focus(null); + } + } + } + /// Handle any user-defined keybinding for the passed keysym and modifiers /// Returns true if the key was handled pub fn handleKeybinding(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32) bool { diff --git a/src/view.zig b/src/view.zig index aa35887..70c81d5 100644 --- a/src/view.zig +++ b/src/view.zig @@ -131,8 +131,10 @@ pub const View = struct { fn handleMap(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void { // Called when the surface is mapped, or ready to display on-screen. const view = @fieldParentPtr(View, "listen_map", listener.?); + const root = view.output.root; view.mapped = true; - view.focus(view.wlr_xdg_surface.surface); + // TODO: remove this hack + root.server.input_manager.seats.first.?.data.focus(view); view.output.root.arrange(); } @@ -141,13 +143,11 @@ pub const View = struct { const root = view.output.root; view.mapped = false; - if (root.focused_view) |current_focus| { - // If the view being unmapped is focused - if (current_focus == view) { - // Focus the previous view. This clears the focus if there are no visible views. - // FIXME: must be fixed in next commit adding focus stack - //root.focusPrevView(); - } + // Inform all seats that the view has been unmapped so they can handle focus + var it = root.server.input_manager.seats.first; + while (it) |node| : (it = node.next) { + const seat = &node.data; + seat.handleViewUnmap(view); } root.arrange(); @@ -181,42 +181,9 @@ pub const View = struct { // // ignore for now // } - fn focus(self: *Self, surface: *c.wlr_surface) void { - const root = self.output.root; - // TODO: remove this hack - const wlr_seat = root.server.input_manager.seats.first.?.data.wlr_seat; - const prev_surface = wlr_seat.keyboard_state.focused_surface; - - if (prev_surface == surface) { - // Don't re-focus an already focused surface. - // TODO: debug message? - return; - } - - root.focused_view = self; - - if (prev_surface != null) { - // Deactivate the previously focused surface. This lets the client know - // it no longer has focus and the client will repaint accordingly, e.g. - // stop displaying a caret. - const prev_xdg_surface = c.wlr_xdg_surface_from_wlr_surface(prev_surface); - _ = c.wlr_xdg_toplevel_set_activated(prev_xdg_surface, false); - } - - // Activate the new surface - _ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, true); - - // Tell the seat to have the keyboard enter this surface. wlroots will keep - // track of this and automatically send key events to the appropriate - // clients without additional work on your part. - const keyboard: *c.wlr_keyboard = c.wlr_seat_get_keyboard(wlr_seat); - c.wlr_seat_keyboard_notify_enter( - wlr_seat, - self.wlr_xdg_surface.surface, - &keyboard.keycodes, - keyboard.num_keycodes, - &keyboard.modifiers, - ); + /// Set the active state of the view to the passed bool + pub fn setActivated(self: Self, activated: bool) void { + _ = c.wlr_xdg_toplevel_set_activated(self.wlr_xdg_surface, activated); } fn isAt(self: Self, lx: f64, ly: f64, surface: *?*c.wlr_surface, sx: *f64, sy: *f64) bool {