Compare commits

..

15 commits

Author SHA1 Message Date
e87a7f214a fix: absorb libcosmic theme-v2 API + reqwest stream feature
Leyoda 2026 – GPLv3
2026-07-03 14:04:49 +02:00
f93673c6f4 chore: use local cosmic-text checkout 2026-06-13 08:39:30 +02:00
821f54693e feat: align terminal with local cosmic stack 2026-06-13 08:39:30 +02:00
Hojjat
7a8ee76e8f chore: support more named keys for keyboard shortcuts 2026-06-13 08:39:30 +02:00
Hojjat Abdollahi
3a60da0213 fix(libcosmic): fix item highlight in wayland context menu 2026-06-13 08:39:30 +02:00
Hojjat
fe2b6ca484 feat: option to open new windows in the current directory 2026-06-13 08:39:30 +02:00
Ashley Wulber
a301e64503 fix: libcosmic updates 2026-06-13 08:39:30 +02:00
Jeremy Soller
a3132220e2 Epoch 1.0.14 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-06-13 08:39:30 +02:00
Hosted Weblate
b6cc5d0e37 i18n: translation updates from weblate
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Hugo Carvalho <hugokarvalho@hotmail.com>
Co-authored-by: oddib <60390653+oddib@users.noreply.github.com>
Co-authored-by: therealmate <hellogaming91@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/ga/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/hu/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/pt/
Translation: Pop OS/COSMIC Term
2026-06-13 08:39:30 +02:00
8db4948de3 chore: resolve local graphics dependencies 2026-05-25 19:37:22 +02:00
437665dd78 chore: use local glyphon lockfile 2026-05-25 18:28:56 +02:00
87128d72d6 fix(keybinds): adapt key_bind.matches to new 3-arg signature
Upstream libcosmic added an Option<&Physical> third parameter to
KeyBind::matches for physical-key matching. We pass None at every call
site since these paths only have the logical key available.

Also refresh Cargo.lock to satisfy the libcosmic-yoda squash rebase
absorbed on 2026-05-25.

Leyoda 2026 – GPLv3
2026-05-25 14:27:37 +02:00
4d05e77e1b chore: use local cosmic-text checkout 2026-05-25 11:34:38 +02:00
65fc96eab8 yoda: refresh local dbus binding lock 2026-05-25 09:55:23 +02:00
6b5a7ee8c8 feat: align terminal with local cosmic stack 2026-05-24 10:27:32 +02:00
22 changed files with 541 additions and 659 deletions

719
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "cosmic-term"
version = "1.2.0"
version = "1.0.14"
authors = ["Jeremy Soller <jeremy@system76.com>"]
edition = "2024"
license = "GPL-3.0-only"

24
debian/changelog vendored
View file

@ -1,27 +1,3 @@
cosmic-term (1.2.0) noble; urgency=medium
* Epoch 1.2.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 30 Jun 2026 12:21:56 -0600
cosmic-term (1.1.0) noble; urgency=medium
* Epoch 1.1.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 23 Jun 2026 08:49:37 -0600
cosmic-term (1.0.16) noble; urgency=medium
* Epoch 1.0.16 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 09 Jun 2026 10:26:45 -0600
cosmic-term (1.0.15) noble; urgency=medium
* Epoch 1.0.15 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 02 Jun 2026 11:03:25 -0600
cosmic-term (1.0.14) noble; urgency=medium
* Epoch 1.0.14 version update

View file

@ -145,5 +145,5 @@ tab-activate = نشّط لسان { $number }
toggle-fullscreen = بدّل ملء الشاشة
type-to-search = اكتب للبحث...
copy-link = انسخ الرابط
tab-new-inherit-working-directory = الألسنة الجديدة والنوافذ تستخدم المجلد الحالي
tab-new-inherit-working-directory-description = افتح الألسنة الجديدة والنوافذ في مجلد العمل الخاص باللسان النشط
tab-new-inherit-working-directory = الألسنة الجديدة تستخدم المجلد الحالي
tab-new-inherit-working-directory-description = افتح ألسنة جديدة في مجلد العمل الخاص باللسان النشط

View file

@ -107,5 +107,5 @@ shortcut-replace-body = { $binding } je již přiřazena k „{ $existing }“.
shortcut-capture-hint = Stiskněte kombinaci kláves
paste-primary = Vložit primární
copy-link = Kopírovat odkaz
tab-new-inherit-working-directory = Nové karty a okna používají aktuální adresář
tab-new-inherit-working-directory-description = Otevírat nové karty a okna v pracovním adresáři aktivní karty
tab-new-inherit-working-directory = Nové karty používají aktuální adresář
tab-new-inherit-working-directory-description = Otevírat nové karty v pracovním adresáři aktivní karty

View file

@ -147,5 +147,5 @@ paste-primary = Primär einfügen
add-another-keybinding = Weitere Tastenbelegung hinzufügen
shortcut-replace-body = { $binding } ist bereits { $existing } zugeordnet. Durch { $new_action } ersetzen?
keywords = Befehl;Shell;Terminal;CLI;
tab-new-inherit-working-directory = Neue Tabs und Fenster verwenden das aktuelle Verzeichnis
tab-new-inherit-working-directory-description = Neue Tabs und Fenster im Arbeitsverzeichnis des aktiven Tabs öffnen
tab-new-inherit-working-directory = Neue Tabs verwenden das aktuelle Verzeichnis
tab-new-inherit-working-directory-description = Neue Tabs im Arbeitsverzeichnis des aktiven Tabs öffnen

View file

@ -150,5 +150,5 @@ paste-primary = Πρωτεύουσα επικόλληση
shortcut-group-other = Άλλες
shortcut-group-zoom = Ζουμ
shortcut-replace-body = Ο συνδυασμός { $binding } έχει ήδη αντιστοιχιστεί στην ενέργεια «{ $existing }». Θέλετε να αντικατασταθεί με την ενέργεια «{ $new_action }»;
tab-new-inherit-working-directory = Οι νέες καρτέλες και παράθυρα χρησιμοποιούν τον τρέχοντα κατάλογο
tab-new-inherit-working-directory-description = Άνοιγμα νέων καρτελών και παραθύρων στον τρέχοντα κατάλογο της ενεργής καρτέλας
tab-new-inherit-working-directory = Οι νέες καρτέλες χρησιμοποιούν τον τρέχοντα κατάλογο
tab-new-inherit-working-directory-description = Άνοιγμα νέων καρτελών στον τρέχοντα κατάλογο της ενεργής καρτέλας

View file

@ -152,5 +152,5 @@ tab-name-placeholder = Nom de l'onglet
tab-rename-title = Renommer l'onglet
comment = Émulateur de terminal pour le bureau COSMIC
keywords = Commande;Shell;Terminal;CLI;
tab-new-inherit-working-directory = Les nouveaux onglets et fenêtres utilisent le répertoire actuel
tab-new-inherit-working-directory-description = Ouvrir les nouveaux onglets et fenêtres dans le répertoire de travail de l'onglet actif
tab-new-inherit-working-directory = Les nouveaux onglets utilisent le répertoire actuel
tab-new-inherit-working-directory-description = Ouvrir les nouveaux onglets dans le répertoire de travail de l'onglet actif

View file

@ -147,5 +147,5 @@ tab-activate = { $number }. lap aktiválása
toggle-fullscreen = Teljes képernyő váltása
type-to-search = Gépelj a kereséshez…
copy-link = Hivatkozás másolása
tab-new-inherit-working-directory = Az új lapok és ablakok az aktuális könyvtárat használják
tab-new-inherit-working-directory-description = Az új lapok és ablakok az aktív lap munkakönyvtárában nyílnak meg
tab-new-inherit-working-directory = Az új lapok az aktuális könyvtárat használják
tab-new-inherit-working-directory-description = Az új lapok az aktív lap munkakönyvtárában nyílnak meg

View file

@ -107,5 +107,5 @@ focus-pane-up = Fokuskan panel ke atas
comment = Emulator terminal untuk desktop COSMIC
keywords = Perintah;Shell;Terminal;CLI;
copy-link = Salin Tautan
tab-new-inherit-working-directory = Tab dan jendela baru menggunakan direktori saat ini
tab-new-inherit-working-directory-description = Buka tab dan jendela baru di direktori pekerjaan tab aktif
tab-new-inherit-working-directory = Tab baru menggunakan direktori saat ini
tab-new-inherit-working-directory-description = Buka tab baru di direktori pekerjaan tab aktif

View file

@ -1,111 +0,0 @@
rename = Navê biguhêrîne
cancel = Têk bibe
name = Nav
delete = Jê bibe
add-profile = Profîlê tevlî bike
find-next = Yê pêş bibîne
splits = Beşkirin
import-errors = Şaşetiyan têxîne
select-all = Hemûyan hilbijêre
new-window = Çarçoveya nû
menu-profiles = Profîl...
new-terminal = Termînala nû
tab-title-description = Sernavê rûgerê yê berdest biguhêrîne
remain-open = Piştî derketina pêvajoyê vekirî bimîne.
working-directory = Rêgeha xebatê
import = Têxîne
profiles = Profîl
tab-title = Sernavê rûgerê
quit = Biqedîne
edit = Biguhêrîne
copy = Jê bigire
export = Derxîne
theme = Rûkar
appearance = Xuyang
close-tab = Rûgerê bigire
light = Ronî
advanced = Pêşketî
support = Piştgirî
default-font = Cûrenivîs
paste = Pêve bike
menu-settings = Sazkarî...
command-line = Rêza fermanê
view = Dîtin
dark = Tarî
hold = Ragire
match-desktop = Li gorî sermaseyê
find-previous = Yê paş bibîne
cosmic-terminal = Termînalê COSMIC
find-placeholder = Bibîne...
advanced-font-settings = Sazkariyên pêşketî yên cûrenivîsê
settings = Sazkarî
font = Cûrenivîs
default-zoom-step = Gavên nêzîkbûnê
clear-scrollback = Dîroka şemitandinê pak bike
open-link = Girêdanê veke
file = Pel
make-default = Bike berdest
new-profile = Profîla nû
profile = Profîl
new-tab = Rûgera nû
comment = Emûlatorê termînalê ji bo sermaseya COSMIC
keywords = Ferman;Shell;Termînal;CLI;
close-window = Çarçoveyê bigire
keyboard-shortcuts = Kurteriyên kilîtdankê
menu-keyboard-shortcuts = Kurteriyên kilîtdankê...
no-shortcuts = Kurterê tune ne
shortcut-group-clipboard = Jêgirtdank
shortcut-group-other = Ên din
shortcut-group-tabs = Rûger
shortcut-group-window = Çarçove
shortcut-group-zoom = Nêzîkbûn
repository = Embar
find = Bibîne
replace = Biguhêrîne
syntax-light = Şêwaza rengê ronî
default-font-weight = Giraniya cûrenivîsê ya normal
zoom-in = Nivîsa mezintir
previous-tab = Rûgera paş
show-headerbar = Sernavê nîşan bide
zoom-out = Nivîsa biçûktir
split-vertical = Stûnî dabeş bike
syntax-dark = Şêwaza rengê tarî
menu-about = Derbarê termînala COSMIC...
menu-color-schemes = Şêwazên rengê...
opacity = Zelaliya paşrûyê
zoom-reset = Mezinahiya nivîsê ya berdest
passwords-title = Borînpeyv
pane-toggle-maximize = Mezinkirinê biguhêrîne
password-input = Borînpeyv
color-schemes = Şêwazên rengê
default-dim-font-weight = Giraniya cûrenivîsê ya sivik
use-bright-bold = Nivîsa qalind ronîtir bike
default-bold-font-weight = Giraniya cûrenivîsê ya qalind
password-input-description = Danasîn
add-password = Borînpeyvê tevlî bike
default-font-stretch = Dirêjahiya cûrenivîsê
menu-password-manager = Borînpeyv...
default-font-size = Mezinahiya cûrenivîsê
split-horizontal = Asoyî dabeş bike
focus-follow-mouse = Balkişandinê nivîsê mişkê dişopîne
next-tab = Rûgera pêş
show-header-description = Sernavê ji menuya rast-tikandin nîşan bide
tab-new-inherit-working-directory = Rûger û çarçoveyên nû peldanka heyî bi kar bîne
tab-new-inherit-working-directory-description = Rûger û çarçoveyên nû di peldanka xebatê ya çalak de veke
add-another-keybinding = Kurteriyeke din tevlî bike
copy-or-sigint = Jê bigire yan jî SIGINT
disable = Neçalak bike
focus-pane-down = Balê bikişîne ser destgeha jêrîn
focus-pane-left = Balê bikişîne ser destgeha çepê
focus-pane-right = Balê bikişîne ser destgeha rastê
focus-pane-up = Balê bikişîne ser destgeha jorîn
password-manager = Rêveberê borînpeyvê
paste-primary = A sereke pêve bike
reset-to-default = Vegere rewşa berdest
shortcut-capture-hint = Kombînasyona kilîtan bitikîne
shortcut-replace-body = { $binding } berê ji bo { $existing } hatiye destnîşankirin. Tu dixwazî bi { $new_action } re biguherînî?
shortcut-replace-title = Kurterê biguhêrînî?
tab-activate = Rûgera { $number } çalak bike
toggle-fullscreen = Dîmendera tijî biguhêrîne
type-to-search = Ji bo lêgerînê binivîse...
copy-link = Girêdanê jê bigire

View file

@ -147,5 +147,5 @@ focus-pane-left = Aktywuj lewy panel
focus-pane-right = Aktywuj prawy panel
focus-pane-up = Aktywuj panel wyżej
copy-link = Skopiuj Odnośnik
tab-new-inherit-working-directory = Nowe karty i okna użyją obecnego katalogu
tab-new-inherit-working-directory-description = Otwieraj nowe karty i okna w katalogu roboczym aktywnej karty
tab-new-inherit-working-directory = Nowe karty użyją obecnego katalogu
tab-new-inherit-working-directory-description = Otwieraj nowe karty w katalogu roboczym aktywnej karty

View file

@ -147,5 +147,5 @@ tab-activate = Ativar aba { $number }
toggle-fullscreen = Alternar para tela cheia
type-to-search = Digite para pesquisar...
copy-link = Copiar link
tab-new-inherit-working-directory = Novas abas e janelas usam o diretório atual
tab-new-inherit-working-directory-description = Abrir novas abas e janelas no diretório de trabalho da aba ativa
tab-new-inherit-working-directory = Novas abas usam o diretório atual
tab-new-inherit-working-directory-description = Abrir novas abas no diretório de trabalho da aba ativa

View file

@ -151,5 +151,5 @@ shortcut-group-other = Andra
shortcut-group-tabs = Flikar
shortcut-group-zoom = Zooma
copy-link = Kopiera länk
tab-new-inherit-working-directory-description = Öppna nya flikar och fönster i den aktiva flikens arbetskatalog
tab-new-inherit-working-directory = Nya flikar och fönster använder aktuell katalog
tab-new-inherit-working-directory-description = Öppna nya flikar i den aktiva flikens arbetskatalog
tab-new-inherit-working-directory = Nya flikar använder aktuell katalog

View file

@ -147,5 +147,5 @@ toggle-fullscreen = 切换全屏
comment = COSMIC 桌面的终端模拟器
keywords = 指令;外壳;终端;命令行界面;
copy-link = 复制链接
tab-new-inherit-working-directory = 新标签和窗口使用当前目录
tab-new-inherit-working-directory-description = 在活动标签的工作目录中打开新标签和窗口
tab-new-inherit-working-directory = 新标签使用当前目录
tab-new-inherit-working-directory-description = 在活动标签的工作目录中打开新标签

View file

@ -147,5 +147,5 @@ copy-link = 複製連結
shortcut-replace-body = { $binding } 已經分配至 { $existing }。 要取代它為 { $new_action }?
clear-scrollback = 清除捲動回朔
comment = COSMIC 桌面終端機模擬器
tab-new-inherit-working-directory = 新分頁和視窗使用目前目錄
tab-new-inherit-working-directory-description = 在作用中分頁的工作目錄開啟新分頁和視窗
tab-new-inherit-working-directory = 新分頁使用目前目錄
tab-new-inherit-working-directory-description = 在作用中分頁的工作目錄開啟新分頁

View file

@ -290,17 +290,11 @@ impl Config {
}
}
pub fn color_scheme_kind(&self, system_theme: &theme::Theme) -> ColorSchemeKind {
match self.app_theme {
AppTheme::Dark => ColorSchemeKind::Dark,
AppTheme::Light => ColorSchemeKind::Light,
AppTheme::System => {
if system_theme.theme_type.is_dark() {
ColorSchemeKind::Dark
} else {
ColorSchemeKind::Light
}
}
pub fn color_scheme_kind(&self) -> ColorSchemeKind {
if self.app_theme.theme().theme_type.is_dark() {
ColorSchemeKind::Dark
} else {
ColorSchemeKind::Light
}
}
@ -364,11 +358,8 @@ impl Config {
}
// Get current syntax theme based on dark mode
pub fn syntax_theme(
&self,
color_scheme_kind: ColorSchemeKind,
profile_id_opt: Option<ProfileId>,
) -> (String, ColorSchemeKind) {
pub fn syntax_theme(&self, profile_id_opt: Option<ProfileId>) -> (String, ColorSchemeKind) {
let color_scheme_kind = self.color_scheme_kind();
let theme_name = match profile_id_opt.and_then(|profile_id| self.profiles.get(&profile_id))
{
Some(profile) => match color_scheme_kind {

View file

@ -186,10 +186,7 @@ fn main() -> Result<(), Box<dyn Error>> {
});
// Terminal config setup
let term_config = term::Config {
scrolling_history: 100_000,
..term::Config::default()
};
let term_config = term::Config::default();
// Set up environmental variables for terminal
tty::setup_env();
// Override TERM for better compatibility
@ -698,12 +695,11 @@ impl App {
}
// Set config of all tabs
let color_scheme_kind = self.config.color_scheme_kind(&theme);
for (_pane, tab_model) in self.pane_model.panes.iter() {
for entity in tab_model.iter() {
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap();
terminal.set_config(&self.config, color_scheme_kind, &self.themes);
terminal.set_config(&self.config, &self.themes);
}
}
}
@ -719,7 +715,6 @@ impl App {
// skip writing config to fs when zoom in/ out
// recalculate the pane due to the changes of zoom_adj value
// but only for the active pane/tab
let color_scheme_kind = self.config.color_scheme_kind(self.core.system_theme());
if let Some(tab_model) = self.pane_model.active() {
for entity in tab_model.iter() {
if tab_model.is_active(entity)
@ -736,7 +731,7 @@ impl App {
}
_ => {}
}
terminal.set_config(&self.config, color_scheme_kind, &self.themes);
terminal.set_config(&self.config, &self.themes);
}
}
}
@ -1566,11 +1561,10 @@ impl App {
self.pane_model.set_focus(pane);
match &self.term_event_tx_opt {
Some(term_event_tx) => {
let color_scheme_kind = self.config.color_scheme_kind(self.core.system_theme());
let colors = self
.themes
.get(&self.config.syntax_theme(color_scheme_kind, profile_id_opt))
.or_else(|| match color_scheme_kind {
.get(&self.config.syntax_theme(profile_id_opt))
.or_else(|| match self.config.color_scheme_kind() {
ColorSchemeKind::Dark => self
.themes
.get(&(config::COSMIC_THEME_DARK.to_string(), ColorSchemeKind::Dark)),
@ -1647,11 +1641,7 @@ impl App {
tab_title_override,
) {
Ok(mut terminal) => {
terminal.set_config(
&self.config,
color_scheme_kind,
&self.themes,
);
terminal.set_config(&self.config, &self.themes);
tab_model
.data_set::<Mutex<Terminal>>(entity, Mutex::new(terminal));
}
@ -1690,7 +1680,7 @@ impl App {
None => {
log::error!(
"failed to find terminal theme {:?}",
self.config.syntax_theme(color_scheme_kind, profile_id_opt)
self.config.syntax_theme(profile_id_opt)
);
//TODO: fall back to known good theme
}
@ -2485,7 +2475,7 @@ impl Application for App {
// Handle configurable keys
for (key_bind, action) in &self.key_binds {
if key_bind.matches(modifiers, &key, Some(&physical)) {
if key_bind.matches(modifiers, &key, None) {
return self.update(action.message(None));
}
}
@ -3140,7 +3130,7 @@ impl Application for App {
&& let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity)
{
let terminal = terminal.lock().unwrap();
let rgb = terminal.effective_color(index);
let rgb = terminal.colors()[index].unwrap_or_default();
let text = f(rgb);
terminal.input_no_scroll(text.into_bytes());
}

View file

@ -208,8 +208,6 @@ pub fn menu_bar<'a>(
//TODO: what to do if there are no profiles?
let color_scheme_kind = config.color_scheme_kind(core.system_theme());
responsive_menu_bar()
.item_height(ItemHeight::Dynamic(40))
.item_width(ItemWidth::Uniform(320))
@ -272,7 +270,7 @@ pub fn menu_bar<'a>(
MenuItem::Button(
fl!("menu-color-schemes"),
None,
Action::ColorSchemes(color_scheme_kind),
Action::ColorSchemes(config.color_scheme_kind()),
),
MenuItem::Button(
fl!("menu-keyboard-shortcuts"),

View file

@ -3,6 +3,10 @@ use cosmic::{
iced::{Event, keyboard::Modifiers, mouse::Button},
};
use crate::terminal::Terminal;
const SCROLL_SPEED: u32 = 3;
#[derive(Default)]
pub struct MouseReporter {
last_movment_x: Option<u32>,
@ -13,30 +17,6 @@ pub struct MouseReporter {
}
impl MouseReporter {
pub fn accumulate_scroll(
&mut self,
delta: ScrollDelta,
cell_width: f32,
cell_height: f32,
) -> (i32, i32) {
match delta {
ScrollDelta::Lines { x, y } => {
self.accumulated_scroll_x += x;
self.accumulated_scroll_y += y;
}
ScrollDelta::Pixels { x, y } => {
self.accumulated_scroll_x += x / cell_width;
self.accumulated_scroll_y += y / cell_height;
}
}
let lines_x = self.accumulated_scroll_x as i32;
let lines_y = self.accumulated_scroll_y as i32;
self.accumulated_scroll_x -= lines_x as f32;
self.accumulated_scroll_y -= lines_y as f32;
(lines_x, lines_y)
}
fn button_number(button: Button) -> Option<u8> {
match button {
Button::Left => Some(0),
@ -198,7 +178,24 @@ impl MouseReporter {
x: u32,
y: u32,
) -> impl Iterator<Item = Vec<u8>> {
let (lines_x, lines_y) = self.accumulate_scroll(delta, term_cell_width, term_cell_height);
let (lines_x, lines_y) = match delta {
ScrollDelta::Lines { x, y } => (x as i32, y as i32),
ScrollDelta::Pixels { x, y } => {
//Accumulate change
self.accumulated_scroll_x += x / term_cell_width;
self.accumulated_scroll_y += y / term_cell_height;
//Resolve lines crossed
let lines_x = self.accumulated_scroll_x as i32;
let lines_y = self.accumulated_scroll_y as i32;
//Subtract accounted lines from accumulators
self.accumulated_scroll_x -= lines_x as f32;
self.accumulated_scroll_y -= lines_y as f32;
(lines_x, lines_y)
}
};
//Resolve modifier flags
let mut modifier_flags = 0;
@ -238,4 +235,26 @@ impl MouseReporter {
term_code.as_bytes().to_vec()
})
}
//Emulate mouse wheel scroll with up/down arrows. Using mouse spec uses
//scroll-back and scroll-forw actions, which moves whole windows like page up/page down.
pub fn report_mouse_wheel_as_arrows(
terminal: &Terminal,
term_cell_width: f32,
term_cell_height: f32,
delta: ScrollDelta,
) {
let (_delta_x, delta_y) = match delta {
ScrollDelta::Lines { x, y } => (x, y),
ScrollDelta::Pixels { x, y } => (x / term_cell_width, y / term_cell_height),
};
//Send delta_y * SCROLL_SPEED number of Up/Down arrows
for _ in 0..(delta_y.abs() as u32 * SCROLL_SPEED) {
if delta_y > 0.0 {
terminal.input_no_scroll(b"\x1B[A".as_slice())
} else if delta_y < 0.0 {
terminal.input_no_scroll(b"\x1B[B".as_slice())
}
}
}
}

View file

@ -21,7 +21,8 @@ use cosmic::{
widget::{pane_grid, segmented_button},
};
use cosmic_text::{
Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, LineEnding, Shaping, Weight, Wrap,
Attrs, AttrsList, Buffer, BufferLine, CacheKeyFlags, Family, LineEnding, Metrics, Shaping,
Weight, Wrap,
};
use indexmap::IndexSet;
use std::{
@ -190,7 +191,7 @@ impl TerminalPaneGrid {
let entity = tab_model.active();
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap();
terminal.set_focused(self.focus == *pane);
terminal.is_focused = self.focus == *pane;
terminal.update();
}
}
@ -200,7 +201,7 @@ impl TerminalPaneGrid {
for entity in tab_model.iter() {
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap();
terminal.set_focused(false);
terminal.is_focused = false;
terminal.update();
}
}
@ -284,7 +285,7 @@ impl Terminal {
let bold_font_weight = app_config.bold_font_weight;
let use_bright_bold = app_config.use_bright_bold;
let metrics = app_config.metrics(0);
let metrics = Metrics::new(14.0, 21.0);
let default_bg = convert_color(&colors, Color::Named(NamedColor::Background));
let default_fg = convert_color(&colors, Color::Named(NamedColor::Foreground));
@ -387,19 +388,6 @@ impl Terminal {
&self.colors
}
pub fn effective_color(&self, index: usize) -> Rgb {
if index == NamedColor::Background as usize {
self.colors[index].unwrap_or_else(|| {
// Allow using an unset background
let [r, g, b, _] =
cosmic_text::Color(WINDOW_BG_COLOR.load(Ordering::SeqCst)).as_rgba();
Rgb { r, g, b }
})
} else {
self.colors[index].unwrap_or_default()
}
}
pub fn default_attrs(&self) -> &Attrs<'static> {
&self.default_attrs
}
@ -416,22 +404,6 @@ impl Terminal {
self.zoom_adj = value;
}
fn set_focused(&mut self, is_focused: bool) {
let focus_changed = self.is_focused != is_focused;
self.is_focused = is_focused;
if focus_changed {
let report_focus = self.term.lock().mode().contains(TermMode::FOCUS_IN_OUT);
if report_focus {
const FOCUS_IN: &[u8] = b"\x1b[I";
const FOCUS_OUT: &[u8] = b"\x1b[O";
let input = if is_focused { FOCUS_IN } else { FOCUS_OUT };
self.input_no_scroll(input);
}
}
}
pub fn redraw(&self) -> bool {
self.buffer.redraw()
}
@ -643,7 +615,6 @@ impl Terminal {
pub fn set_config(
&mut self,
config: &AppConfig,
color_scheme_kind: ColorSchemeKind,
themes: &HashMap<(String, ColorSchemeKind), Colors>,
) {
let mut update_cell_size = false;
@ -670,7 +641,7 @@ impl Terminal {
update_cell_size = true;
}
if self.bold_font_weight.0 != config.bold_font_weight {
if self.bold_font_weight.0 != config.font_weight {
self.bold_font_weight = Weight(config.bold_font_weight);
update_cell_size = true;
}
@ -686,9 +657,7 @@ impl Terminal {
update_cell_size = true;
}
if let Some(colors) =
themes.get(&config.syntax_theme(color_scheme_kind, self.profile_id_opt))
{
if let Some(colors) = themes.get(&config.syntax_theme(self.profile_id_opt)) {
let mut changed = false;
for i in 0..color::COUNT {
if self.colors[i] != colors[i] {
@ -1057,9 +1026,10 @@ impl Terminal {
x: u32,
y: u32,
) {
let is_sgr = self.term.lock().mode().contains(TermMode::SGR_MOUSE);
let term_lock = self.term.lock();
let mode = term_lock.mode();
if is_sgr {
if mode.contains(TermMode::SGR_MOUSE) {
let codes = self.mouse_reporter.sgr_mouse_wheel_scroll(
self.size().cell_width,
self.size().cell_height,
@ -1073,29 +1043,12 @@ impl Terminal {
self.notifier.notify(code);
}
} else {
self.scroll_as_arrows(delta);
}
}
pub fn scroll_as_arrows(&mut self, delta: ScrollDelta) {
let cell_width = self.size().cell_width;
let cell_height = self.size().cell_height;
let (_, lines_y) = self
.mouse_reporter
.accumulate_scroll(delta, cell_width, cell_height);
let is_app_cursor = self.term.lock().mode().contains(TermMode::APP_CURSOR);
let (up, down) = if is_app_cursor {
(&b"\x1BOA"[..], &b"\x1BOB"[..])
} else {
(&b"\x1B[A"[..], &b"\x1B[B"[..])
};
const SCROLL_SPEED: u32 = 3;
for _ in 0..(lines_y.unsigned_abs() * SCROLL_SPEED) {
if lines_y > 0 {
self.input_no_scroll(up)
} else if lines_y < 0 {
self.input_no_scroll(down)
}
MouseReporter::report_mouse_wheel_as_arrows(
self,
self.size().cell_width,
self.size().cell_height,
delta,
);
}
}
}

View file

@ -45,7 +45,10 @@ use std::{
time::{Duration, Instant},
};
use crate::{Action, Terminal, TerminalScroll, menu::MenuState, terminal::Metadata};
use crate::{
Action, Terminal, TerminalScroll, menu::MenuState, mouse_reporter::MouseReporter,
terminal::Metadata,
};
const AUTOSCROLL_INTERVAL: Duration = Duration::from_millis(100);
@ -940,7 +943,7 @@ where
..
}) if state.is_focused && named == modified_named => {
for key_bind in self.key_binds.keys() {
if key_bind.matches(*modifiers, &Key::Named(*named), Some(physical_key)) {
if key_bind.matches(*modifiers, &Key::Named(*named), None) {
shell.capture_event();
return;
}
@ -1127,11 +1130,10 @@ where
modifiers,
key,
physical_key,
modified_key,
..
}) if state.is_focused => {
for key_bind in self.key_binds.keys() {
if key_bind.matches(*modifiers, key, Some(physical_key)) {
if key_bind.matches(*modifiers, key, None) {
shell.capture_event();
return;
@ -1174,10 +1176,11 @@ where
}
}
(false, true, _, true) => {
// Ctrl+Shift+<key>: send 0x1F (C-_) on '_', matching xterm/gnome-terminal.
// Use `modified_key` so this works regardless of which physical key
// produces '_' on the user's layout.
if *modified_key == Key::Character("_".into()) {
//This is normally Ctrl+Minus, but since that
//is taken by zoom, we send that code for
//Ctrl+Underline instead, like xterm and
//gnome-terminal
if *key == Key::Character("_".into()) {
terminal.input_scroll(b"\x1F".as_slice());
shell.capture_event();
}
@ -1548,27 +1551,26 @@ where
let row = y / terminal.size().cell_height;
terminal.scroll_mouse(*delta, &state.modifiers, col as u32, row as u32);
} else if terminal.term.lock().mode().contains(TermMode::ALT_SCREEN) {
terminal.scroll_as_arrows(*delta);
MouseReporter::report_mouse_wheel_as_arrows(
&terminal,
terminal.size().cell_width,
terminal.size().cell_height,
*delta,
);
shell.capture_event();
} else {
match delta {
ScrollDelta::Lines { x: _, y } => {
// High-resolution wheels deliver a notch as many
// fractional deltas; accumulate them so nothing
// below one whole line is lost (see
// accumulate_wheel_lines).
//TODO: this adjustment is just a guess!
state.scroll_pixels = 0.0;
let (lines, remainder) =
accumulate_wheel_lines(*y, state.scroll_lines);
state.scroll_lines = remainder;
let lines = (-y * 6.0) as i32;
if lines != 0 {
terminal.scroll(TerminalScroll::Delta(lines));
terminal.scroll(TerminalScroll::Delta(-lines));
}
shell.capture_event();
}
ScrollDelta::Pixels { x: _, y } => {
//TODO: this adjustment is just a guess!
state.scroll_lines = 0.0;
state.scroll_pixels -= y * 6.0;
let mut lines = 0;
let metrics = terminal.with_buffer(|buffer| buffer.metrics());
@ -1734,26 +1736,6 @@ fn update_active_regex_match(
}
}
/// Whole lines scrolled per unit of wheel `Lines` delta.
//TODO: this adjustment is just a guess!
const SCROLL_LINE_MULTIPLIER: f32 = 6.0;
/// Convert a wheel `Lines` delta into whole lines to scroll, accumulating the
/// fractional remainder across events.
///
/// High-resolution wheels deliver a single physical notch as many fractional
/// deltas (e.g. `y = 0.125`). Scaling by [`SCROLL_LINE_MULTIPLIER`] and
/// truncating per event discards anything below one whole line, so most of
/// those events would scroll nothing. Carrying the remainder forward means the
/// fractions add up and a full notch scrolls the intended amount.
///
/// Returns `(whole_lines, new_accumulator)`.
fn accumulate_wheel_lines(y: f32, accumulator: f32) -> (i32, f32) {
let total = accumulator + y * SCROLL_LINE_MULTIPLIER;
let lines = total.trunc() as i32;
(lines, total - lines as f32)
}
fn shade(color: cosmic_text::Color, is_focused: bool) -> cosmic_text::Color {
if is_focused {
color
@ -1955,7 +1937,6 @@ pub struct State {
dragging: Option<Dragging>,
is_focused: bool,
scroll_pixels: f32,
scroll_lines: f32,
scrollbar_rect: Cell<Rectangle<f32>>,
autoscroll: DragAutoscroll,
preedit: Option<input_method::Preedit>,
@ -1970,7 +1951,6 @@ impl State {
dragging: None,
is_focused: false,
scroll_pixels: 0.0,
scroll_lines: 0.0,
scrollbar_rect: Cell::new(Rectangle::default()),
autoscroll: DragAutoscroll::new(AUTOSCROLL_INTERVAL),
preedit: None,
@ -2050,38 +2030,7 @@ fn ss3(code: &str, modifiers: u8) -> Option<Vec<u8>> {
#[cfg(test)]
mod tests {
use super::{EdgeScrollDirection, accumulate_wheel_lines, edge_scroll_adjustment};
#[test]
fn wheel_lines_single_fractional_event_keeps_remainder() {
// A high-res wheel notch arrives as fractional deltas. A lone 0.125
// event is less than one whole line, but must not be discarded.
let (lines, remainder) = accumulate_wheel_lines(0.125, 0.0);
assert_eq!(lines, 0);
assert!((remainder - 0.75).abs() < f32::EPSILON);
}
#[test]
fn wheel_lines_accumulate_into_whole_lines() {
// Eight 0.125 deltas make up one physical notch and must scroll the
// full SCROLL_LINE_MULTIPLIER (6) lines, not be lost to truncation.
let mut accumulator = 0.0;
let mut total = 0;
for _ in 0..8 {
let (lines, remainder) = accumulate_wheel_lines(0.125, accumulator);
accumulator = remainder;
total += lines;
}
assert_eq!(total, 6);
}
#[test]
fn wheel_lines_preserve_direction() {
let (down, _) = accumulate_wheel_lines(0.25, 0.0);
let (up, _) = accumulate_wheel_lines(-0.25, 0.0);
assert_eq!(down, 1);
assert_eq!(up, -1);
}
use super::{EdgeScrollDirection, edge_scroll_adjustment};
const BUFFER_HEIGHT: f32 = 200.0;
const CELL_HEIGHT: f32 = 20.0;