aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeon Henrik Plickat <leonhenrik.plickat@stud.uni-goettingen.de>2020-10-03 22:09:15 +0200
committerIsaac Freund <ifreund@ifreund.xyz>2020-10-05 23:03:57 +0200
commitb67ea748a386d1050e2019d20dfe69e99bf7bad6 (patch)
tree0424bd314bb6f61b2eb15c790cacb4672af17d35
parent27b666dbbabb1635fb523445ac154253098f73fd (diff)
downloadriver-b67ea748a386d1050e2019d20dfe69e99bf7bad6.tar.gz
river-b67ea748a386d1050e2019d20dfe69e99bf7bad6.tar.xz
Implement configurable view opacity with fade effect
-rwxr-xr-xcontrib/config.sh2
-rw-r--r--doc/riverctl.1.scd15
-rw-r--r--river/Config.zig15
-rw-r--r--river/Root.zig2
-rw-r--r--river/Seat.zig6
-rw-r--r--river/View.zig96
-rw-r--r--river/XdgToplevel.zig4
-rw-r--r--river/command.zig3
-rw-r--r--river/command/opacity.zig79
-rw-r--r--river/render.zig11
10 files changed, 228 insertions, 5 deletions
diff --git a/contrib/config.sh b/contrib/config.sh
index 620b29f..3b51bbe 100755
--- a/contrib/config.sh
+++ b/contrib/config.sh
@@ -100,3 +100,5 @@ riverctl float-filter-add "popup"
# Set app-ids of views which should use client side decorations
riverctl csd-filter-add "gedit"
+# Set opacity and fade effect
+# riverctl opacity 1.0 0.75 0.0 0.1 20
diff --git a/doc/riverctl.1.scd b/doc/riverctl.1.scd
index bbbbafa..143d57e 100644
--- a/doc/riverctl.1.scd
+++ b/doc/riverctl.1.scd
@@ -162,6 +162,21 @@ that tag 1 through 9 are visible.
- move-view
- resize-view
+*opacity* _focused-opacity_ _unfocused-opacity_ _starting-opacity_ _opacity-step_ _opacity-delta-t_
+ Set the server side opacity of views.
+
+ _focused-opacity_ sets the opacity of the focused window, _unfocused-opacity_
+ the opacity of every unfocused window while _starting-opacity_ sets the
+ opacity a window will have at startup before immediately transitioning to
+ either the focused or unfocused opacity. These settings require a floating
+ point number from 0.0 (fully transparent) to 1.0 (fully opaque).
+
+ Opacity transitions can be animated. _opacity-step_ sets the amount the
+ opacity should be increased or decreased per step of the transition. It
+ requires a floating point number from 0.05 to 1.0. If set to 1.0, animations
+ are disabled. _opacity-delta-t_ sets the time between the transition steps
+ in milliseconds.
+
*outer-padding* _pixels_
Set the padding around the edge of the screen to _pixels_.
diff --git a/river/Config.zig b/river/Config.zig
index bb9b7a6..04b1893 100644
--- a/river/Config.zig
+++ b/river/Config.zig
@@ -66,6 +66,21 @@ csd_filter: std.ArrayList([]const u8),
/// The selected focus_follows_cursor mode
focus_follows_cursor: FocusFollowsCursorMode = .disabled,
+/// The opacity of the focused view
+view_opacity_focused: f32 = 1.0,
+
+/// The opacity of unfocused views
+view_opacity_unfocused: f32 = 1.0,
+
+/// The starting opacity of new views
+view_opacity_initial: f32 = 1.0,
+
+/// View opacity transition step
+view_opacity_delta: f32 = 1.0,
+
+/// Time between view opacity transition steps in msec
+view_opacity_delta_t: u31 = 20,
+
pub fn init() !Self {
var self = Self{
.mode_to_id = std.StringHashMap(usize).init(util.gpa),
diff --git a/river/Root.zig b/river/Root.zig
index 5705ea3..d07884e 100644
--- a/river/Root.zig
+++ b/river/Root.zig
@@ -250,6 +250,8 @@ fn commitTransaction(self: *Self) void {
view.current = view.pending;
view.dropSavedBuffers();
+
+ view.commitOpacityTransition();
}
if (view_tags_changed) output.sendViewTags();
diff --git a/river/Seat.zig b/river/Seat.zig
index 77bfed8..bb6e557 100644
--- a/river/Seat.zig
+++ b/river/Seat.zig
@@ -188,6 +188,9 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// activated state.
if (build_options.xwayland and self.focused.view.impl == .xwayland_view)
c.wlr_xwayland_surface_activate(self.focused.view.impl.xwayland_view.wlr_xwayland_surface, false);
+ if (self.focused.view.pending.focus == 0) {
+ self.focused.view.pending.target_opacity = self.input_manager.server.config.view_opacity_unfocused;
+ }
}
c.wlr_seat_keyboard_clear_focus(self.wlr_seat);
@@ -200,6 +203,7 @@ pub fn setFocusRaw(self: *Self, new_focus: FocusTarget) void {
// activated state.
if (build_options.xwayland and target_view.impl == .xwayland_view)
c.wlr_xwayland_surface_activate(target_view.impl.xwayland_view.wlr_xwayland_surface, true);
+ target_view.pending.target_opacity = self.input_manager.server.config.view_opacity_focused;
},
.layer => |target_layer| std.debug.assert(self.focused_output == target_layer.output),
.none => {},
@@ -281,7 +285,7 @@ pub fn handleMapping(self: *Self, keysym: c.xkb_keysym_t, modifiers: u32, releas
if (out) |s| {
const stdout = std.io.getStdOut().outStream();
stdout.print("{}", .{s}) catch
- |err| log.err(.command, "{}: write to stdout failed {}", .{ args[0], err });
+ |err| log.err(.command, "{}: write to stdout failed {}", .{ args[0], err });
}
return true;
}
diff --git a/river/View.zig b/river/View.zig
index 3bcc684..df23d12 100644
--- a/river/View.zig
+++ b/river/View.zig
@@ -1,6 +1,7 @@
// This file is part of river, a dynamic tiling wayland compositor.
//
// Copyright 2020 Isaac Freund
+// Copyright 2020 Leon Henrik Plickat
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -65,6 +66,9 @@ const State = struct {
float: bool = false,
fullscreen: bool = false,
+
+ /// Opacity the view is transitioning to
+ target_opacity: f32,
};
const SavedBuffer = struct {
@@ -109,14 +113,27 @@ saved_buffers: std.ArrayList(SavedBuffer),
/// view returns to floating mode.
float_box: Box = undefined,
+/// The current opacity of this view
+opacity: f32,
+
+/// Opacity change timer event source
+opacity_timer: ?*c.wl_event_source = null,
+
draw_borders: bool = true,
pub fn init(self: *Self, output: *Output, tags: u32, surface: anytype) void {
self.* = .{
.output = output,
- .current = .{ .tags = tags },
- .pending = .{ .tags = tags },
+ .current = .{
+ .tags = tags,
+ .target_opacity = output.root.server.config.view_opacity_initial,
+ },
+ .pending = .{
+ .tags = tags,
+ .target_opacity = output.root.server.config.view_opacity_initial,
+ },
.saved_buffers = std.ArrayList(SavedBuffer).init(util.gpa),
+ .opacity = output.root.server.config.view_opacity_initial,
};
if (@TypeOf(surface) == *c.wlr_xdg_surface) {
@@ -337,6 +354,8 @@ pub fn shouldTrackConfigure(self: Self) bool {
pub fn map(self: *Self) void {
const root = self.output.root;
+ self.pending.target_opacity = self.output.root.server.config.view_opacity_unfocused;
+
log.debug(.server, "view '{}' mapped", .{self.getTitle()});
// Add the view to the stack of its output
@@ -365,6 +384,10 @@ pub fn unmap(self: *Self) void {
self.destroying = true;
+ if (self.opacity_timer != null) {
+ self.killOpacityTimer();
+ }
+
// 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) {
@@ -379,3 +402,72 @@ pub fn unmap(self: *Self) void {
root.startTransaction();
}
+
+/// Change the opacity of a view by config.view_opacity_delta.
+/// If the target opacity was reached, return true.
+fn incrementOpacity(self: *Self) bool {
+ // TODO damage view when implementing damage based rendering
+ const config = &self.output.root.server.config;
+ if (self.opacity < self.current.target_opacity) {
+ self.opacity += config.view_opacity_delta;
+ if (self.opacity < self.current.target_opacity) return false;
+ } else {
+ self.opacity -= config.view_opacity_delta;
+ if (self.opacity > self.current.target_opacity) return false;
+ }
+ self.opacity = self.current.target_opacity;
+ return true;
+}
+
+/// Destroy a views opacity timer
+fn killOpacityTimer(self: *Self) void {
+ if (c.wl_event_source_remove(self.opacity_timer) < 0) unreachable;
+ self.opacity_timer = null;
+}
+
+/// Set the timeout on a views opacity timer
+fn armOpacityTimer(self: *Self) void {
+ const delta_t = self.output.root.server.config.view_opacity_delta_t;
+ if (c.wl_event_source_timer_update(self.opacity_timer, delta_t) < 0) {
+ log.err(.view, "failed to update opacity timer", .{});
+ self.killOpacityTimer();
+ }
+}
+
+/// Called by the opacity timer
+fn handleOpacityTimer(data: ?*c_void) callconv(.C) c_int {
+ const self = util.voidCast(Self, data.?);
+ if (self.incrementOpacity()) {
+ self.killOpacityTimer();
+ } else {
+ self.armOpacityTimer();
+ }
+ return 0;
+}
+
+/// Create an opacity timer for a view and arm it
+fn attachOpacityTimer(self: *Self) void {
+ const server = self.output.root.server;
+ self.opacity_timer = c.wl_event_loop_add_timer(
+ c.wl_display_get_event_loop(server.wl_display),
+ handleOpacityTimer,
+ self,
+ ) orelse {
+ log.err(.view, "failed to create opacity timer for view '{}'", .{self.getTitle()});
+ return;
+ };
+ self.armOpacityTimer();
+}
+
+/// Commit an opacity transition
+pub fn commitOpacityTransition(self: *Self) void {
+ if (self.opacity == self.current.target_opacity) return;
+
+ // A running timer can handle a target_opacity change
+ if (self.opacity_timer != null) return;
+
+ // Do the first step now, if that step was not enough, attach timer
+ if (!self.incrementOpacity()) {
+ self.attachOpacityTimer();
+ }
+}
diff --git a/river/XdgToplevel.zig b/river/XdgToplevel.zig
index fcd74f4..1693271 100644
--- a/river/XdgToplevel.zig
+++ b/river/XdgToplevel.zig
@@ -251,8 +251,10 @@ fn handleCommit(listener: ?*c.wl_listener, data: ?*c_void) callconv(.C) void {
view.pending_serial = null;
if (view.shouldTrackConfigure())
view.output.root.notifyConfigured()
- else
+ else {
view.current = view.pending;
+ view.commitOpacityTransition();
+ }
} else {
// If the client has not yet acked our configure, we need to send a
// frame done event so that it commits another buffer. These
diff --git a/river/command.zig b/river/command.zig
index bfe0727..c9935d5 100644
--- a/river/command.zig
+++ b/river/command.zig
@@ -49,6 +49,7 @@ const str_to_impl_fn = [_]struct {
.{ .name = "map-pointer", .impl = @import("command/map.zig").mapPointer },
.{ .name = "mod-master-count", .impl = @import("command/mod_master_count.zig").modMasterCount },
.{ .name = "mod-master-factor", .impl = @import("command/mod_master_factor.zig").modMasterFactor },
+ .{ .name = "opacity", .impl = @import("command/opacity.zig").opacity },
.{ .name = "outer-padding", .impl = @import("command/config.zig").outerPadding },
.{ .name = "send-to-output", .impl = @import("command/send_to_output.zig").sendToOutput },
.{ .name = "set-focused-tags", .impl = @import("command/tags.zig").setFocusedTags },
@@ -73,6 +74,7 @@ pub const Error = error{
InvalidCharacter,
InvalidDirection,
InvalidRgba,
+ InvalidValue,
UnknownOption,
OutOfMemory,
Other,
@@ -113,6 +115,7 @@ pub fn errToMsg(err: Error) [:0]const u8 {
Error.InvalidCharacter => "invalid character in argument",
Error.InvalidDirection => "invalid direction. Must be 'next' or 'previous'",
Error.InvalidRgba => "invalid color format, must be #RRGGBB or #RRGGBBAA",
+ Error.InvalidValue => "invalid value",
Error.OutOfMemory => "out of memory",
Error.Other => unreachable,
};
diff --git a/river/command/opacity.zig b/river/command/opacity.zig
new file mode 100644
index 0000000..7cf8e86
--- /dev/null
+++ b/river/command/opacity.zig
@@ -0,0 +1,79 @@
+// This file is part of river, a dynamic tiling wayland compositor.
+//
+// Copyright 2020 Leon Henrik Plickat
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+const std = @import("std");
+
+const Error = @import("../command.zig").Error;
+const Seat = @import("../Seat.zig");
+const View = @import("../View.zig");
+const ViewStack = @import("../view_stack.zig").ViewStack;
+
+fn opacityUpdateFilter(view: *View, context: void) bool {
+ // We want to update all views
+ return true;
+}
+
+pub fn opacity(
+ allocator: *std.mem.Allocator,
+ seat: *Seat,
+ args: []const []const u8,
+ out: *?[]const u8,
+) Error!void {
+ if (args.len < 6) return Error.NotEnoughArguments;
+ if (args.len > 6) return Error.TooManyArguments;
+
+ const server = seat.input_manager.server;
+
+ // Focused opacity
+ server.config.view_opacity_focused = try std.fmt.parseFloat(f32, args[1]);
+ if (server.config.view_opacity_focused < 0.0 or server.config.view_opacity_focused > 1.0)
+ return Error.InvalidValue;
+
+ // Unfocused opacity
+ server.config.view_opacity_unfocused = try std.fmt.parseFloat(f32, args[2]);
+ if (server.config.view_opacity_unfocused < 0.0 or server.config.view_opacity_unfocused > 1.0)
+ return Error.InvalidValue;
+
+ // Starting opacity for new views
+ server.config.view_opacity_initial = try std.fmt.parseFloat(f32, args[3]);
+ if (server.config.view_opacity_initial < 0.0 or server.config.view_opacity_initial > 1.0)
+ return Error.InvalidValue;
+
+ // Opacity transition step
+ server.config.view_opacity_delta = try std.fmt.parseFloat(f32, args[4]);
+ if (server.config.view_opacity_delta < 0.0 or server.config.view_opacity_delta > 1.0)
+ return Error.InvalidValue;
+
+ // Time between step
+ server.config.view_opacity_delta_t = try std.fmt.parseInt(u31, args[5], 10);
+ if (server.config.view_opacity_delta_t < 1) return Error.InvalidValue;
+
+ // Update opacity of all views
+ // Unmapped views will be skipped, however their opacity gets updated on map anyway
+ var oit = server.root.outputs.first;
+ while (oit) |onode| : (oit = onode.next) {
+ var vit = ViewStack(View).iter(onode.data.views.first, .forward, {}, opacityUpdateFilter);
+ while (vit.next()) |vnode| {
+ if (vnode.current.focus > 0) {
+ vnode.pending.target_opacity = server.config.view_opacity_focused;
+ } else {
+ vnode.pending.target_opacity = server.config.view_opacity_unfocused;
+ }
+ }
+ }
+ server.root.startTransaction();
+}
diff --git a/river/render.zig b/river/render.zig
index 65a4ab6..857478b 100644
--- a/river/render.zig
+++ b/river/render.zig
@@ -36,6 +36,8 @@ const SurfaceRenderData = struct {
output_y: i32,
when: *c.timespec,
+
+ opacity: f32,
};
pub fn renderOutput(output: *Output) void {
@@ -138,6 +140,7 @@ fn renderLayer(output: Output, layer: std.TailQueue(LayerSurface), now: *c.times
.output_x = layer_surface.box.x,
.output_y = layer_surface.box.y,
.when = now,
+ .opacity = 1.0,
};
c.wlr_layer_surface_v1_for_each_surface(
layer_surface.wlr_layer_surface,
@@ -162,6 +165,7 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
.height = @intCast(c_int, saved_buffer.box.height),
},
saved_buffer.transform,
+ view.opacity,
);
} else {
// Since there is no stashed buffer, we are not in the middle of
@@ -171,6 +175,7 @@ fn renderView(output: Output, view: *View, now: *c.timespec) void {
.output_x = view.current.box.x - view.surface_box.x,
.output_y = view.current.box.y - view.surface_box.y,
.when = now,
+ .opacity = view.opacity,
};
view.forEachSurface(renderSurfaceIterator, &rdata);
@@ -191,6 +196,7 @@ fn renderDragIcons(output: Output, now: *c.timespec) void {
.output_y = @floatToInt(i32, drag_icon.seat.cursor.wlr_cursor.y) +
drag_icon.wlr_drag_icon.surface.*.sy - output_box.*.y,
.when = now,
+ .opacity = 1.0,
};
c.wlr_surface_for_each_surface(drag_icon.wlr_drag_icon.surface, renderSurfaceIterator, &rdata);
}
@@ -209,6 +215,7 @@ fn renderXwaylandUnmanaged(output: Output, now: *c.timespec) void {
.output_x = wlr_xwayland_surface.x - output_box.*.x,
.output_y = wlr_xwayland_surface.y - output_box.*.y,
.when = now,
+ .opacity = 1.0,
};
c.wlr_surface_for_each_surface(wlr_xwayland_surface.surface, renderSurfaceIterator, &rdata);
}
@@ -233,6 +240,7 @@ fn renderSurfaceIterator(
.height = surface.?.current.height,
},
surface.?.current.transform,
+ rdata.opacity,
);
c.wlr_surface_send_frame_done(surface, rdata.when);
@@ -245,6 +253,7 @@ fn renderTexture(
wlr_texture: ?*c.wlr_texture,
wlr_box: c.wlr_box,
transform: c.wl_output_transform,
+ opacity: f32,
) void {
const texture = wlr_texture orelse return;
var box = wlr_box;
@@ -262,7 +271,7 @@ fn renderTexture(
// This takes our matrix, the texture, and an alpha, and performs the actual
// rendering on the GPU.
- _ = c.wlr_render_texture_with_matrix(output.getRenderer(), texture, &matrix, 1.0);
+ _ = c.wlr_render_texture_with_matrix(output.getRenderer(), texture, &matrix, opacity);
}
fn renderBorders(output: Output, view: *View, now: *c.timespec) void {