| 171 |
171 |
|
keyboard: None,
|
| 172 |
172 |
|
modifiers: Modifiers::default(),
|
| 173 |
173 |
|
cursor_phase: true,
|
|
174 |
+ |
focused: false,
|
| 174 |
175 |
|
pending_redraw: true,
|
| 175 |
176 |
|
};
|
| 176 |
177 |
|
app.surface.configure(&app.device, &app.surface_config);
|
| 205 |
206 |
|
}
|
| 206 |
207 |
|
Ok(n) => {
|
| 207 |
208 |
|
app.parser.advance(&mut app.grid, &buf[..n]);
|
|
209 |
+ |
if let Some(title) = app.grid.take_pending_title() {
|
|
210 |
+ |
app.chrome.xdg_window.set_title(title);
|
|
211 |
+ |
}
|
| 208 |
212 |
|
app.cursor_phase = !app.cursor_phase;
|
| 209 |
213 |
|
app.pending_redraw = true;
|
| 210 |
214 |
|
}
|
| 293 |
297 |
|
/// Cursor "activity light" phase. Toggles on every PTY-read chunk. Idle
|
| 294 |
298 |
|
/// prompt = steady on; heavy output = visible strobe.
|
| 295 |
299 |
|
cursor_phase: bool,
|
|
300 |
+ |
/// True while our surface has keyboard focus. Cursor dims when unfocused
|
|
301 |
+ |
/// so you can tell at a glance which window is receiving input.
|
|
302 |
+ |
focused: bool,
|
| 296 |
303 |
|
pending_redraw: bool,
|
| 297 |
304 |
|
}
|
| 298 |
305 |
|
|
| 354 |
361 |
|
color: bg,
|
| 355 |
362 |
|
});
|
| 356 |
363 |
|
}
|
|
364 |
+ |
if cell.attrs.underline {
|
|
365 |
+ |
fills.push(BgFill {
|
|
366 |
+ |
x,
|
|
367 |
+ |
y: y + cell_h_px - 2.0 * s,
|
|
368 |
+ |
w: cell_w_px,
|
|
369 |
+ |
h: 1.5 * s,
|
|
370 |
+ |
color: fg,
|
|
371 |
+ |
});
|
|
372 |
+ |
}
|
| 357 |
373 |
|
let c = cell.c;
|
| 358 |
374 |
|
if c != ' ' && c != '\0' {
|
| 359 |
375 |
|
cells.push(CellDraw { c, x, y, color: fg });
|
| 364 |
380 |
|
if cursor.visible {
|
| 365 |
381 |
|
let cx = (PAD_X + cursor.col as f32 * CELL_ADVANCE) * s;
|
| 366 |
382 |
|
let cy = (PAD_Y + cursor.row as f32 * CELL_HEIGHT) * s;
|
| 367 |
|
- |
let color = if app.cursor_phase {
|
|
383 |
+ |
let base = if app.cursor_phase {
|
| 368 |
384 |
|
CURSOR_COLOR_ON
|
| 369 |
385 |
|
} else {
|
| 370 |
386 |
|
CURSOR_COLOR_DIM
|
| 371 |
387 |
|
};
|
|
388 |
+ |
// Dim to a hollow-looking ghost when the window doesn't have focus.
|
|
389 |
+ |
let color = if app.focused {
|
|
390 |
+ |
base
|
|
391 |
+ |
} else {
|
|
392 |
+ |
[base[0], base[1], base[2], base[3] * 0.35]
|
|
393 |
+ |
};
|
| 372 |
394 |
|
let (w, h, ox, oy) = match cursor_shape {
|
| 373 |
395 |
|
CursorShape::Block => (cell_w_px, cell_h_px, 0.0, 0.0),
|
| 374 |
396 |
|
CursorShape::Underline => (cell_w_px, 2.0 * s, 0.0, cell_h_px - 2.0 * s),
|
| 510 |
532 |
|
_: &[u32],
|
| 511 |
533 |
|
_: &[Keysym],
|
| 512 |
534 |
|
) {
|
|
535 |
+ |
self.focused = true;
|
|
536 |
+ |
self.pending_redraw = true;
|
| 513 |
537 |
|
}
|
| 514 |
538 |
|
fn leave(
|
| 515 |
539 |
|
&mut self,
|
| 519 |
543 |
|
_: &wl_surface::WlSurface,
|
| 520 |
544 |
|
_: u32,
|
| 521 |
545 |
|
) {
|
|
546 |
+ |
self.focused = false;
|
|
547 |
+ |
self.pending_redraw = true;
|
| 522 |
548 |
|
}
|
| 523 |
549 |
|
fn press_key(
|
| 524 |
550 |
|
&mut self,
|
| 608 |
634 |
|
Keysym::Delete => return tilde(3),
|
| 609 |
635 |
|
_ => {}
|
| 610 |
636 |
|
}
|
| 611 |
|
- |
event
|
| 612 |
|
- |
.utf8
|
| 613 |
|
- |
.as_ref()
|
| 614 |
|
- |
.map(|s| s.as_bytes().to_vec())
|
| 615 |
|
- |
.unwrap_or_default()
|
|
637 |
+ |
let utf8 = event.utf8.as_deref().unwrap_or_default();
|
|
638 |
+ |
if utf8.is_empty() {
|
|
639 |
+ |
return Vec::new();
|
|
640 |
+ |
}
|
|
641 |
+ |
// xterm "Alt sends escape" convention: Alt+letter prepends ESC. Deferred
|
|
642 |
+ |
// dead-key handling: users with Compose/deadkey layouts may want this off
|
|
643 |
+ |
// via config once TOML lands.
|
|
644 |
+ |
if mods.alt {
|
|
645 |
+ |
let mut out = Vec::with_capacity(utf8.len() + 1);
|
|
646 |
+ |
out.push(0x1b);
|
|
647 |
+ |
out.extend_from_slice(utf8.as_bytes());
|
|
648 |
+ |
out
|
|
649 |
+ |
} else {
|
|
650 |
+ |
utf8.as_bytes().to_vec()
|
|
651 |
+ |
}
|
| 616 |
652 |
|
}
|
| 617 |
653 |
|
|
| 618 |
654 |
|
/// xterm modifier number = 1 + Shift + 2*Alt + 4*Ctrl + 8*Meta.
|