mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 21:32:13 +03:00
wezterm: add window background image and opacity options
I thought it would be cute to add an option to add a background image to the window. While playing around with the parameters, I accidentally implemented window transparency on X11/Wayland, provided that you have a compositing window manager. I don't know of the transparency also works on macOS or Windows as of yet; will try that out once I push this commit. This commit introduces three new configuration options explained below. In the future I'd like to allow specifying equivalent settings in a color scheme, and then that would allow setting per-pane background images. ```lua return { --[[ Specifies the path to a background image attachment file. The file can be any image format that the rust `image` crate is able to identify and load. A window background image is rendered into the background of the window before any other content. The image will be scaled to fit the window. If the path is not absolute, then it will taken as being relative to the directory containing wezterm.lua. ]] window_background_image = "/some/file", --[[ Specifies the alpha value to use when rendering the background of the window. The background is taken either from the window_background_image, or if there is none, the background color of the cell in the current position. The default is 1.0 which is 100% opaque. Setting it to a number between 0.0 and 1.0 will allow for the screen behind the window to "shine through" to varying degrees. This only works on systems with a compositing window manager. Setting opacity to a value other than 1.0 can impact render performance. ]] window_background_opacity = 1.0, --[[ Specifies the alpha value to use when applying the default background color in a cell. This is useful to apply a kind of "tint" to the background image if either window_background_image or window_background_opacity are in used. It can be a number between 0.0 and 1.0. The default is 0.0 Larger numbers increase the amount of the color scheme's background color that is applied over the background image. This can be useful to increase effective contrast for text that is rendered over the top. ]] window_background_tint = 0.0, } ``` refs: https://github.com/wez/wezterm/issues/141
This commit is contained in:
parent
2c0c89a97b
commit
f2cbc182cd
@ -691,6 +691,45 @@ pub struct Config {
|
||||
#[serde(default)]
|
||||
pub window_padding: WindowPadding,
|
||||
|
||||
/// Specifies the path to a background image attachment file.
|
||||
/// The file can be any image format that the rust `image`
|
||||
/// crate is able to identify and load.
|
||||
/// A window background image is rendered into the background
|
||||
/// of the window before any other content.
|
||||
///
|
||||
/// The image will be scaled to fit the window.
|
||||
#[serde(default)]
|
||||
pub window_background_image: Option<PathBuf>,
|
||||
|
||||
/// Specifies the alpha value to use when rendering the background
|
||||
/// of the window. The background is taken either from the
|
||||
/// window_background_image, or if there is none, the background
|
||||
/// color of the cell in the current position.
|
||||
/// The default is 1.0 which is 100% opaque. Setting it to a number
|
||||
/// between 0.0 and 1.0 will allow for the screen behind the window
|
||||
/// to "shine through" to varying degrees.
|
||||
/// This only works on systems with a compositing window manager.
|
||||
/// Setting opacity to a value other than 1.0 can impact render
|
||||
/// performance.
|
||||
#[serde(default = "default_one_point_oh")]
|
||||
pub window_background_opacity: f32,
|
||||
|
||||
/// Specifies the alpha value to use when applying the default
|
||||
/// background color in a cell. This is useful to apply a kind
|
||||
/// of "tint" to the background image if either window_background_image
|
||||
/// or window_background_opacity are in used.
|
||||
///
|
||||
/// It can be a number between 0.0 and 1.0.
|
||||
/// The default is 0.0
|
||||
///
|
||||
/// Larger numbers increase the amount of the color scheme's
|
||||
/// background color that is applied over the background image.
|
||||
///
|
||||
/// This can be useful to increase effective contrast for text
|
||||
/// that is rendered over the top.
|
||||
#[serde(default = "default_zero_point_oh")]
|
||||
pub window_background_tint: f32,
|
||||
|
||||
/// Specifies how often a blinking cursor transitions between visible
|
||||
/// and invisible, expressed in milliseconds.
|
||||
/// Setting this to 0 disables blinking.
|
||||
@ -757,6 +796,14 @@ pub struct Config {
|
||||
pub enable_csi_u_key_encoding: bool,
|
||||
}
|
||||
|
||||
fn default_zero_point_oh() -> f32 {
|
||||
0.0
|
||||
}
|
||||
|
||||
fn default_one_point_oh() -> f32 {
|
||||
1.0
|
||||
}
|
||||
|
||||
fn default_tab_max_width() -> usize {
|
||||
16
|
||||
}
|
||||
@ -946,6 +993,12 @@ impl Config {
|
||||
*font_dir = dir;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = self.window_background_image.as_ref() {
|
||||
if !path.is_absolute() {
|
||||
cfg.window_background_image.replace(config_dir.join(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.font_rules.is_empty() {
|
||||
|
@ -9,7 +9,9 @@ in vec2 o_cursor;
|
||||
in vec4 o_cursor_color;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform bool window_bg_layer;
|
||||
uniform bool bg_and_line_layer;
|
||||
uniform bool has_background_image;
|
||||
uniform sampler2D glyph_tex;
|
||||
|
||||
out vec4 color;
|
||||
@ -33,7 +35,23 @@ vec4 multiply(vec4 src, vec4 dst) {
|
||||
}
|
||||
|
||||
void main() {
|
||||
if (bg_and_line_layer) {
|
||||
if (window_bg_layer) {
|
||||
if (o_has_color == 2.0) {
|
||||
// We're the window background image.
|
||||
color = texture(glyph_tex, o_tex);
|
||||
// Apply window_background_opacity to the background image
|
||||
color.a = o_bg_color.a;
|
||||
} else if (!has_background_image) {
|
||||
// If there is no background image then take the cell background
|
||||
color = o_bg_color;
|
||||
} else {
|
||||
// Nothing else should render on the background layer
|
||||
// color = vec4(0,0,0,0);
|
||||
discard;
|
||||
}
|
||||
} else if (bg_and_line_layer) {
|
||||
// Note that o_bg_color is set to transparent if the background
|
||||
// color is "default" and there is a window background attachment
|
||||
color = o_bg_color;
|
||||
|
||||
// Sample the underline glyph texture for this location.
|
||||
@ -44,7 +62,7 @@ void main() {
|
||||
// if the underline glyph isn't transparent in this position then
|
||||
// we take the text fg color, otherwise we'll leave the color
|
||||
// at the background color.
|
||||
color.rgb = o_fg_color.rgb;
|
||||
color = o_fg_color;
|
||||
}
|
||||
|
||||
// Similar to the above: if the cursor texture isn't transparent
|
||||
@ -53,14 +71,20 @@ void main() {
|
||||
// in the section above.
|
||||
vec4 cursor_outline = texture(glyph_tex, o_cursor);
|
||||
if (cursor_outline.a != 0.0) {
|
||||
color.rgb = o_cursor_color.rgb;
|
||||
color = o_cursor_color;
|
||||
}
|
||||
|
||||
} else {
|
||||
color = texture(glyph_tex, o_tex);
|
||||
if (o_has_color == 0.0) {
|
||||
// if it's not a color emoji, tint with the fg_color
|
||||
color.rgb = o_fg_color.rgb;
|
||||
if (o_has_color == 2.0) {
|
||||
// Don't render the background image on anything other than
|
||||
// the window_bg_layer.
|
||||
// color = vec4(0,0,0,0);
|
||||
discard;
|
||||
} else {
|
||||
color = texture(glyph_tex, o_tex);
|
||||
if (o_has_color == 0.0) {
|
||||
// if it's not a color emoji, tint with the fg_color
|
||||
color.rgb = o_fg_color.rgb;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,16 @@ pub struct Vertex {
|
||||
pub cursor_color: (f32, f32, f32, f32),
|
||||
pub bg_color: (f32, f32, f32, f32),
|
||||
pub fg_color: (f32, f32, f32, f32),
|
||||
// We use a float for this because I can't get
|
||||
// bool or integer values to work:
|
||||
// "bool can't be an in in the vertex shader"
|
||||
//
|
||||
// has_color is effectively an enum with three
|
||||
// possible values:
|
||||
// 0.0 -> a regular monochrome text glyph
|
||||
// 1.0 -> a color emoji glyph
|
||||
// 2.0 -> a full color texture attached as the
|
||||
// background image of the window
|
||||
pub has_color: f32,
|
||||
}
|
||||
::window::glium::implement_vertex!(
|
||||
@ -56,6 +65,7 @@ pub struct Quads {
|
||||
pub row_starts: Vec<usize>,
|
||||
/// The vertex index for the first vertex of the scroll bar thumb
|
||||
pub scroll_thumb: usize,
|
||||
pub background_image: usize,
|
||||
}
|
||||
|
||||
pub struct MappedQuads<'a> {
|
||||
@ -87,6 +97,13 @@ impl<'a> MappedQuads<'a> {
|
||||
vert: &mut self.mapping[start..start + VERTICES_PER_CELL],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn background_image<'b>(&'b mut self) -> Quad<'b> {
|
||||
let start = self.quads.background_image;
|
||||
Quad {
|
||||
vert: &mut self.mapping[start..start + VERTICES_PER_CELL],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Quads {
|
||||
@ -129,6 +146,14 @@ impl<'a> Quad<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark this quad as a background image.
|
||||
/// Mutually exclusive with set_has_color.
|
||||
pub fn set_is_background_image(&mut self) {
|
||||
for v in self.vert.iter_mut() {
|
||||
v.has_color = 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_fg_color(&mut self, color: Color) {
|
||||
let color = color.to_tuple_rgba();
|
||||
for v in self.vert.iter_mut() {
|
||||
|
@ -201,6 +201,10 @@ impl OpenGLRenderState {
|
||||
idx
|
||||
};
|
||||
|
||||
// Background image fills the entire window background
|
||||
quads.background_image =
|
||||
define_quad(width / -2.0, height / -2.0, width / 2.0, height / 2.0) as usize;
|
||||
|
||||
for y in 0..num_rows {
|
||||
let y_pos = (height / -2.0) + (y as f32 * cell_height) + padding_top;
|
||||
|
||||
|
@ -48,8 +48,9 @@ use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::{Duration, Instant};
|
||||
use termwiz::color::RgbColor;
|
||||
use termwiz::color::{ColorAttribute, RgbColor};
|
||||
use termwiz::hyperlink::Hyperlink;
|
||||
use termwiz::image::ImageData;
|
||||
use termwiz::surface::{CursorShape, CursorVisibility};
|
||||
use wezterm_term::color::ColorPalette;
|
||||
use wezterm_term::input::LastMouseClick;
|
||||
@ -273,6 +274,8 @@ pub struct TermWindow {
|
||||
tab_state: RefCell<HashMap<TabId, TabState>>,
|
||||
pane_state: RefCell<HashMap<PaneId, PaneState>>,
|
||||
|
||||
window_background: Option<Arc<ImageData>>,
|
||||
|
||||
/// Gross workaround for managing async keyboard fetching
|
||||
/// just for middle mouse button paste function
|
||||
clipboard_contents: Arc<Mutex<Option<String>>>,
|
||||
@ -706,6 +709,7 @@ impl WindowCallbacks for TermWindow {
|
||||
|
||||
let guts = Box::new(Self {
|
||||
window: None,
|
||||
window_background: self.window_background.clone(),
|
||||
focused: None,
|
||||
mux_window_id,
|
||||
fonts: Rc::clone(&self.fonts),
|
||||
@ -874,9 +878,59 @@ pub fn effective_right_padding(config: &ConfigHandle, render_metrics: &RenderMet
|
||||
}
|
||||
}
|
||||
|
||||
fn load_background_image(config: &ConfigHandle) -> Option<Arc<ImageData>> {
|
||||
match &config.window_background_image {
|
||||
Some(p) => match std::fs::read(p) {
|
||||
Ok(data) => {
|
||||
log::error!("loaded {}", p.display());
|
||||
Some(Arc::new(ImageData::with_raw_data(data)))
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to load window_background_image {}: {}",
|
||||
p.display(),
|
||||
err
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn reload_background_image(
|
||||
config: &ConfigHandle,
|
||||
image: &Option<Arc<ImageData>>,
|
||||
) -> Option<Arc<ImageData>> {
|
||||
match &config.window_background_image {
|
||||
Some(p) => match std::fs::read(p) {
|
||||
Ok(data) => {
|
||||
if let Some(existing) = image {
|
||||
if existing.data() == data {
|
||||
return Some(Arc::clone(existing));
|
||||
}
|
||||
}
|
||||
Some(Arc::new(ImageData::with_raw_data(data)))
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to load window_background_image {}: {}",
|
||||
p.display(),
|
||||
err
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl TermWindow {
|
||||
pub fn new_window(mux_window_id: MuxWindowId) -> anyhow::Result<()> {
|
||||
let config = configuration();
|
||||
|
||||
let window_background = load_background_image(&config);
|
||||
|
||||
let fontconfig = Rc::new(FontConfiguration::new());
|
||||
let mux = Mux::get().unwrap();
|
||||
let tab = mux.get_active_tab_for_window(mux_window_id).unwrap();
|
||||
@ -933,6 +987,7 @@ impl TermWindow {
|
||||
dimensions.pixel_height,
|
||||
Box::new(Self {
|
||||
window: None,
|
||||
window_background,
|
||||
focused: None,
|
||||
mux_window_id,
|
||||
fonts: fontconfig,
|
||||
@ -1249,6 +1304,8 @@ impl TermWindow {
|
||||
::window::os::windows::use_dead_keys(config.use_dead_keys);
|
||||
}
|
||||
|
||||
self.window_background = reload_background_image(&config, &self.window_background);
|
||||
|
||||
let mux = Mux::get().unwrap();
|
||||
let window = match mux.get_window(self.mux_window_id) {
|
||||
Some(window) => window,
|
||||
@ -2569,7 +2626,8 @@ impl TermWindow {
|
||||
|
||||
let cursor_border_color = rgbcolor_to_window_color(palette.cursor_border);
|
||||
let foreground = rgbcolor_to_window_color(palette.foreground);
|
||||
let background = rgbcolor_to_window_color(palette.background);
|
||||
let background_alpha = (config.window_background_opacity * 255.0) as u8;
|
||||
let background = rgbcolor_alpha_to_window_color(palette.background, background_alpha);
|
||||
|
||||
if self.show_tab_bar && pos.index == 0 {
|
||||
let tab_dims = RenderableDimensions {
|
||||
@ -2637,6 +2695,29 @@ impl TermWindow {
|
||||
quad.set_cursor_color(rgbcolor_to_window_color(background_color));
|
||||
}
|
||||
|
||||
{
|
||||
let mut quad = quads.background_image();
|
||||
let white_space = gl_state.util_sprites.white_space.texture_coords();
|
||||
quad.set_underline(white_space);
|
||||
quad.set_cursor(white_space);
|
||||
quad.set_has_color(false);
|
||||
|
||||
let color = Color::rgba(0, 0, 0, background_alpha);
|
||||
quad.set_texture_adjust(0., 0., 0., 0.);
|
||||
|
||||
quad.set_texture(white_space);
|
||||
if let Some(im) = self.window_background.as_ref() {
|
||||
if let Ok(sprite) = gl_state.glyph_cache.borrow_mut().cached_image(im) {
|
||||
quad.set_texture(sprite.texture_coords());
|
||||
quad.set_is_background_image();
|
||||
}
|
||||
}
|
||||
quad.set_cursor_color(color);
|
||||
quad.set_fg_color(color);
|
||||
quad.set_bg_color(background);
|
||||
quad.set_cursor_color(color);
|
||||
}
|
||||
|
||||
let selrange = self.selection(pos.pane.pane_id()).range.clone();
|
||||
|
||||
for (line_idx, line) in lines.iter().enumerate() {
|
||||
@ -2696,7 +2777,9 @@ impl TermWindow {
|
||||
.magnify_filter(MagnifySamplerFilter::Nearest)
|
||||
.minify_filter(MinifySamplerFilter::Nearest);
|
||||
|
||||
// Pass 1: Draw backgrounds, strikethrough and underline
|
||||
let has_background_image = self.window_background.is_some();
|
||||
|
||||
// Pass 1: Draw backgrounds
|
||||
frame.draw(
|
||||
&*vb,
|
||||
&gl_state.glyph_index_buffer,
|
||||
@ -2704,7 +2787,9 @@ impl TermWindow {
|
||||
&uniform! {
|
||||
projection: projection,
|
||||
glyph_tex: glyph_tex,
|
||||
bg_and_line_layer: true,
|
||||
window_bg_layer: true,
|
||||
bg_and_line_layer: false,
|
||||
has_background_image: has_background_image,
|
||||
},
|
||||
&draw_params,
|
||||
)?;
|
||||
@ -2731,7 +2816,7 @@ impl TermWindow {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Pass 2: Draw glyphs
|
||||
// Pass 2: strikethrough and underline
|
||||
frame.draw(
|
||||
&*vb,
|
||||
&gl_state.glyph_index_buffer,
|
||||
@ -2739,7 +2824,24 @@ impl TermWindow {
|
||||
&uniform! {
|
||||
projection: projection,
|
||||
glyph_tex: glyph_tex,
|
||||
window_bg_layer: false,
|
||||
bg_and_line_layer: true,
|
||||
has_background_image: has_background_image,
|
||||
},
|
||||
&draw_params,
|
||||
)?;
|
||||
|
||||
// Pass 3: Draw glyphs
|
||||
frame.draw(
|
||||
&*vb,
|
||||
&gl_state.glyph_index_buffer,
|
||||
&gl_state.program,
|
||||
&uniform! {
|
||||
projection: projection,
|
||||
glyph_tex: glyph_tex,
|
||||
window_bg_layer: false,
|
||||
bg_and_line_layer: false,
|
||||
has_background_image: has_background_image,
|
||||
},
|
||||
&draw_params,
|
||||
)?;
|
||||
@ -2782,6 +2884,7 @@ impl TermWindow {
|
||||
};
|
||||
let style = self.fonts.match_style(params.config, attrs);
|
||||
|
||||
let bg_is_default = attrs.background == ColorAttribute::Default;
|
||||
let bg_color = params.palette.resolve_bg(attrs.background);
|
||||
let fg_color = match attrs.foreground {
|
||||
wezterm_term::color::ColorAttribute::Default => {
|
||||
@ -2809,19 +2912,29 @@ impl TermWindow {
|
||||
_ => params.palette.resolve_fg(attrs.foreground),
|
||||
};
|
||||
|
||||
let (fg_color, bg_color) = {
|
||||
let (fg_color, bg_color, bg_is_default) = {
|
||||
let mut fg = fg_color;
|
||||
let mut bg = bg_color;
|
||||
let mut bg_is_default = bg_is_default;
|
||||
|
||||
if attrs.reverse() {
|
||||
std::mem::swap(&mut fg, &mut bg);
|
||||
// reversed, so don't let it be transparent
|
||||
bg_is_default = false;
|
||||
}
|
||||
|
||||
(fg, bg)
|
||||
(fg, bg, bg_is_default)
|
||||
};
|
||||
|
||||
let glyph_color = rgbcolor_to_window_color(fg_color);
|
||||
let bg_color = rgbcolor_to_window_color(bg_color);
|
||||
let bg_color = if bg_is_default {
|
||||
rgbcolor_alpha_to_window_color(
|
||||
bg_color,
|
||||
(params.config.window_background_tint * 255.0) as u8,
|
||||
)
|
||||
} else {
|
||||
rgbcolor_to_window_color(bg_color)
|
||||
};
|
||||
|
||||
// Shape the printable text from this cluster
|
||||
let glyph_info = {
|
||||
@ -3010,13 +3123,18 @@ impl TermWindow {
|
||||
// Even though we don't have a cell for these, they still
|
||||
// hold the cursor or the selection so we need to compute
|
||||
// the colors in the usual way.
|
||||
|
||||
let (glyph_color, bg_color, cursor_shape) = self.compute_cell_fg_bg(
|
||||
params.stable_line_idx,
|
||||
cell_idx,
|
||||
params.cursor,
|
||||
¶ms.selection,
|
||||
params.foreground,
|
||||
params.background,
|
||||
if self.window_background.is_some() {
|
||||
Color::rgba(0, 0, 0, 0)
|
||||
} else {
|
||||
params.background
|
||||
},
|
||||
params.palette,
|
||||
params.pos.is_active,
|
||||
);
|
||||
@ -4029,7 +4147,11 @@ impl TermWindow {
|
||||
}
|
||||
|
||||
fn rgbcolor_to_window_color(color: RgbColor) -> Color {
|
||||
Color::rgba(color.red, color.green, color.blue, 0xff)
|
||||
rgbcolor_alpha_to_window_color(color, 0xff)
|
||||
}
|
||||
|
||||
fn rgbcolor_alpha_to_window_color(color: RgbColor, alpha: u8) -> Color {
|
||||
Color::rgba(color.red, color.green, color.blue, alpha)
|
||||
}
|
||||
|
||||
fn window_mods_to_termwiz_mods(modifiers: ::window::Modifiers) -> termwiz::input::Modifiers {
|
||||
|
@ -10,7 +10,9 @@ in vec2 cursor;
|
||||
in vec4 cursor_color;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform bool window_bg_layer;
|
||||
uniform bool bg_and_line_layer;
|
||||
uniform bool has_background_image;
|
||||
|
||||
out vec2 o_tex;
|
||||
out vec4 o_fg_color;
|
||||
@ -20,6 +22,14 @@ out vec2 o_underline;
|
||||
out vec2 o_cursor;
|
||||
out vec4 o_cursor_color;
|
||||
|
||||
// Returns a position that is outside of the viewport,
|
||||
// such that this vertex effectively won't contribute
|
||||
// the scene being rendered.
|
||||
// There may be a better way to do this.
|
||||
vec4 off_screen() {
|
||||
return vec4(100.0, 100.0, 100.0, 100.0);
|
||||
}
|
||||
|
||||
void main() {
|
||||
o_tex = tex;
|
||||
o_has_color = has_color;
|
||||
@ -29,7 +39,24 @@ void main() {
|
||||
o_cursor = cursor;
|
||||
o_cursor_color = cursor_color;
|
||||
|
||||
if (bg_and_line_layer) {
|
||||
if (window_bg_layer) {
|
||||
if (o_has_color == 2.0) {
|
||||
// Background image takes up its full coordinates
|
||||
gl_Position = projection * vec4(position, 0.0, 1.0);
|
||||
} else if (!has_background_image) {
|
||||
// If there is no background attachment, then we're
|
||||
// rendering the cell background color and need to
|
||||
// fill the cell with that color
|
||||
gl_Position = projection * vec4(position, 0.0, 1.0);
|
||||
} else {
|
||||
// Nothing else should render on the background layer
|
||||
gl_Position = off_screen();
|
||||
}
|
||||
} else if (o_has_color == 2.0) {
|
||||
// If we're the background image and we're not rendering
|
||||
// the background layer, then move this off screen
|
||||
gl_Position = off_screen();
|
||||
} else if (bg_and_line_layer) {
|
||||
// Want to fill the whole cell when painting backgrounds
|
||||
gl_Position = projection * vec4(position, 0.0, 1.0);
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user