Compare commits

...

19 commits

Author SHA1 Message Date
81dc7d3eeb chore: fige Cargo.lock (epoch-1.9.0, [patch] locaux)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 21:03:54 +02:00
083e09f97e merge: epoch-1.9.0 d'upstream
# Conflicts:
#	Cargo.lock
#	src/main.rs
#	src/menu.rs
2026-09-24 20:52:15 +02:00
Hojjat
9129277bed chore: update libcosmic to fix esc handling in menus 2026-09-23 11:55:55 -06:00
Hojjat
78eaba9db1 chore: tweak context menu items order and wording 2026-09-15 19:09:43 -04:00
Michael Aaron Murphy
d25c2108c0 chore: update cosmic-files 2026-09-15 19:09:43 -04:00
Hojjat
120b4fdb12 feat: use libcosmic's context_menu widget for the terminal context menu 2026-09-15 19:09:43 -04:00
Michael Aaron Murphy
4cd8f8d605 Epoch 1.9.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-09-15 19:09:43 -04:00
Michael Murphy
1d9e5784d2
i18n: translation update from Hosted Weblate (#908)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC
Term](https://hosted.weblate.org/projects/pop-os/cosmic-term/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-term/matrix-auto.svg)
2026-09-15 21:57:44 +02:00
Hosted Weblate
327febcc92
i18n: translation updates from weblate
Co-authored-by: Amadɣas <massiin@proton.me>
Co-authored-by: Blauen <butterflee555@gmail.com>
Co-authored-by: CYAXXX <85353920+CYAXXX@users.noreply.github.com>
Co-authored-by: Geeson Wan <wang14240@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kurt Harren Catindig <kurtharrencatindig03@gmail.com>
Co-authored-by: Quentin PAGÈS <quentinantonin@free.fr>
Co-authored-by: Satakun Utama <galaxytomostore@gmail.com>
Co-authored-by: VandaL <vandalhj@gmail.com>
Co-authored-by: Zahid Rizky Fakhri <zahidrizkyfakhri@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/id/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/kab/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/kmr/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/oc/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/pl/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/th/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/zh_Hant/
Translation: Pop OS/COSMIC Term
2026-09-13 01:56:16 +02:00
99842bde03 chore: fige Cargo.lock 2026-09-10 18:12:37 +02:00
5fc081e257 Merge tag 'epoch-1.8.0' into local/yoda-main
Epoch 1.8.0

# Conflicts:
#	Cargo.lock
2026-09-10 17:57:28 +02:00
b8fce5a272 yoda: elements de bloc U+2580-259F peints en quads natifs
Le glyphe de police d'un bloc ne couvre que ~1 em alors que la cellule
fait line_height (~1.4 em) : rangées de blocs trouees par l'interligne,
bords creneles, rendu flou aux echelles fractionnees.

Desormais le caractere est remplace par une espace (aucun glyphe) et le
remplissage voyage dans Metadata jusqu'au tracé, ou terminal_box le
peint en quad GPU couvrant exactement la fraction de cellule (huitiemes,
quadrants, trames alpha). Consecutive cells de meme metadata fusionnent
en un seul quad : jointif par construction, aligne sur la grille, net a
toute echelle. C'est l'approche de kitty/WezTerm. Tout TUI a blocs
profite (bannieres, jauges htop/btop, barres de progression).

Couverture totale de la plage + geometrie bornee, 2 tests.
2026-09-04 16:53:35 +02:00
Levi Portenier
4f0f0b6e4e
Epoch 1.8.0 version update (#911)
Generated by cosmic-epoch scripts/version-update.sh
2026-09-02 12:25:35 -06:00
Jeremy Soller
7945b9ffc6 Epoch 1.8.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-09-02 08:16:02 -06:00
41203ab5f1 chore: fige Cargo.lock tel que resolu apres le merge 1.7.0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-27 07:58:51 +02:00
f3f87e59da Merge tag 'epoch-1.7.0' into local/yoda-main
Epoch 1.7.0

# Conflicts:
#	Cargo.lock
2026-08-27 07:52:54 +02:00
Jeremy Soller
909a0d31aa Epoch 1.7.0 version update
Generated by cosmic-epoch scripts/version-update.sh
2026-08-25 12:12:31 -06:00
Michael Murphy
7df9265243
i18n: translation update from Hosted Weblate (#902)
Translations update from [Hosted Weblate](https://hosted.weblate.org)
for [Pop OS/COSMIC
Term](https://hosted.weblate.org/projects/pop-os/cosmic-term/).



Current translation status:

![Weblate translation
status](https://hosted.weblate.org/widget/pop-os/cosmic-term/matrix-auto.svg)
2026-08-24 20:55:33 +02:00
Hosted Weblate
dfa59f3ca4
i18n: translation updates from weblate
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jens Peter Nielsen <mail@jp-nielsen.dk>
Co-authored-by: Jim Spentzos <jimspentzos2000@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/da/
Translate-URL: https://hosted.weblate.org/projects/pop-os/cosmic-term/el/
Translation: Pop OS/COSMIC Term
2026-08-21 21:17:19 +02:00
19 changed files with 670 additions and 750 deletions

718
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

18
debian/changelog vendored
View file

@ -1,3 +1,21 @@
cosmic-term (1.9.0) noble; urgency=medium
* Epoch 1.9.0 version update
-- Michael Murphy <michael@mmurphy.dev> Mon, 14 Sep 2026 19:50:47 +0200
cosmic-term (1.8.0) noble; urgency=medium
* Epoch 1.8.0 version update
-- Jeremy Soller <jeremy@system76.com> Wed, 02 Sep 2026 08:15:55 -0600
cosmic-term (1.7.0) noble; urgency=medium
* Epoch 1.7.0 version update
-- Jeremy Soller <jeremy@system76.com> Tue, 25 Aug 2026 09:21:20 -0600
cosmic-term (1.6.0) noble; urgency=medium cosmic-term (1.6.0) noble; urgency=medium
* Epoch 1.6.0 version update * Epoch 1.6.0 version update

View file

@ -1,4 +1,6 @@
tar-ignore=.github tar-ignore=.github
tar-ignore=.vscode tar-ignore=target/.rustc_info.json
tar-ignore=target/CACHEDIR.TAG
tar-ignore=target/debug
tar-ignore=target/release
tar-ignore=vendor tar-ignore=vendor
tar-ignore=target

View file

@ -1,7 +1,7 @@
rename = Omdøb rename = Omdøb
delete = Slet delete = Slet
name = Navn name = Navn
cancel = Annuller cancel = Afbryd
quit = Afslut quit = Afslut
find-next = Find næste find-next = Find næste
select-all = Vælg alle select-all = Vælg alle

View file

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

View file

@ -63,6 +63,7 @@ show-pane-borders = Show pane borders
### Advanced ### Advanced
advanced = Advanced advanced = Advanced
show-headerbar = Show header show-headerbar = Show header
window-header = Window header
show-header-description = Reveal the header from the right-click menu show-header-description = Reveal the header from the right-click menu
tab-new-inherit-working-directory = New tabs and windows use current directory tab-new-inherit-working-directory = New tabs and windows use current directory
tab-new-inherit-working-directory-description = Open new tabs and windows in the active tab's working directory tab-new-inherit-working-directory-description = Open new tabs and windows in the active tab's working directory
@ -152,7 +153,7 @@ next-tab = Next tab
previous-tab = Previous tab previous-tab = Previous tab
split-horizontal = Split horizontal split-horizontal = Split horizontal
split-vertical = Split vertical split-vertical = Split vertical
pane-toggle-maximize = Toggle maximized pane-toggle-maximize = Maximize or restore pane
menu-color-schemes = Color schemes... menu-color-schemes = Color schemes...
menu-settings = Settings... menu-settings = Settings...
menu-about = About COSMIC Terminal... menu-about = About COSMIC Terminal...

0
i18n/fil/cosmic_term.ftl Normal file
View file

View file

@ -4,7 +4,7 @@ repository = Repositori
support = Dukungan support = Dukungan
color-schemes = Skema warna color-schemes = Skema warna
cosmic-terminal = Terminal COSMIC cosmic-terminal = Terminal COSMIC
rename = Ganti nama rename = Ganti Nama
export = Ekspor export = Ekspor
delete = Hapus delete = Hapus
import = Impor import = Impor

View file

@ -1,9 +1,9 @@
cancel = Sefsex cancel = Semmet
name = Isem name = Isem
delete = kkes delete = kkes
rename = Snifel isem rename = Snifel isem
quit = Tuffɣa quit = Ffeɣ
support = Tallalt support = Tallelt
repository = Asarsay repository = Asarsay
settings = Iɣewwaṛen settings = Iɣewwaṛen
appearance = Timeẓri appearance = Timeẓri
@ -14,7 +14,7 @@ paste = Senteḍ
select-all = Fren akk select-all = Fren akk
view = Wali view = Wali
menu-settings = Iɣewwaṛen… menu-settings = Iɣewwaṛen…
match-desktop = Amṣada d tnarit match-desktop = Semṣada akked tnarit
file = Afaylu file = Afaylu
new-window = Asfaylu amaynut new-window = Asfaylu amaynut
edit = Ẓreg edit = Ẓreg

View file

@ -27,7 +27,7 @@ light = Ronî
advanced = Pêşketî advanced = Pêşketî
support = Piştgirî support = Piştgirî
default-font = Cûrenivîs default-font = Cûrenivîs
paste = Pêve bike paste = Pê ve bike
menu-settings = Sazkarî... menu-settings = Sazkarî...
command-line = Rêza fermanê command-line = Rêza fermanê
view = Dîtin view = Dîtin
@ -100,7 +100,7 @@ focus-pane-left = Balê bikişîne ser destgeha çepê
focus-pane-right = Balê bikişîne ser destgeha rastê focus-pane-right = Balê bikişîne ser destgeha rastê
focus-pane-up = Balê bikişîne ser destgeha jorîn focus-pane-up = Balê bikişîne ser destgeha jorîn
password-manager = Rêveberê borînpeyvê password-manager = Rêveberê borînpeyvê
paste-primary = A sereke pêve bike paste-primary = A sereke pê ve bike
reset-to-default = Vegere rewşa berdest reset-to-default = Vegere rewşa berdest
shortcut-capture-hint = Kombînasyona kilîtan bitikîne 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-body = { $binding } berê ji bo { $existing } hatiye destnîşankirin. Tu dixwazî bi { $new_action } re biguherînî?

View file

@ -1,7 +1,14 @@
settings = Paramètres settings = Configuracion
appearance = Aparéncia appearance = Aparéncia
theme = Tèma theme = Tèma
match-desktop = Assortir al burèu match-desktop = Assortir al burèu
dark = Fosc dark = Escur
light = Clar light = Clar
file = Fichièr file = Fichièr
name = Nom
repository = Depaus
cancel = Anullar
menu-settings = Configuracion...
quit = Quitar
delete = Suprimir
replace = Remplaçar

View file

@ -1,5 +1,5 @@
cosmic-terminal = Terminal COSMIC cosmic-terminal = Terminal COSMIC
comment = Emulator terminala dla pulpitu COSMIC comment = Emulator terminala dla środowiska COSMIC
keywords = Cli;WierszPoleceń;Powłoka;Terminal; keywords = Cli;WierszPoleceń;Powłoka;Terminal;
new-terminal = Nowy terminal new-terminal = Nowy terminal
@ -73,7 +73,7 @@ advanced = Zaawansowane
show-headerbar = Pokaż pasek nagłówka show-headerbar = Pokaż pasek nagłówka
show-header-description = Ukazuje pasek nagłówka z menu wybieranego prawym przyciskiem myszki show-header-description = Ukazuje pasek nagłówka z menu wybieranego prawym przyciskiem myszki
# Find # Find
find-placeholder = Szukaj… find-placeholder = Wyszukaj…
find-previous = Znajdź poprzedni find-previous = Znajdź poprzedni
find-next = Znajdź następny find-next = Znajdź następny
@ -96,7 +96,7 @@ edit = Edytuj
copy = Kopiuj copy = Kopiuj
paste = Wklej paste = Wklej
select-all = Zaznacz wszystko select-all = Zaznacz wszystko
find = Szukaj find = Wyszukaj
clear-scrollback = Wyczyść przewijanie clear-scrollback = Wyczyść przewijanie
## View ## View

View file

@ -39,8 +39,8 @@ settings = การตั้งค่า
appearance = ลักษณะ appearance = ลักษณะ
theme = ธีม theme = ธีม
match-desktop = ใช้ตามธีมหน้าจอหลัก match-desktop = ใช้ตามธีมหน้าจอหลัก
dark = ธีมมืด dark = มืด
light = ธีมสว่าง light = สว่าง
syntax-dark = โทนสีมืด syntax-dark = โทนสีมืด
syntax-light = โทนสีสว่าง syntax-light = โทนสีสว่าง
default-zoom-step = ระยะซูม default-zoom-step = ระยะซูม
@ -109,3 +109,8 @@ pane-toggle-maximize = ขยายให้ใหญ่สุด
menu-color-schemes = โทนสี... menu-color-schemes = โทนสี...
menu-settings = การตั้งค่า... menu-settings = การตั้งค่า...
menu-about = เกี่ยวกับ เทอร์มินัล COSMIC... menu-about = เกี่ยวกับ เทอร์มินัล COSMIC...
cancel = ยกเลิก
reset-to-default = รีเซ็ตเป็นค่าเริ่มต้น
type-to-search = พิมพ์เพื่อค้นหา
add-another-keybinding = เพิ่มคีย์ลัดเพิ่มเติม
replace = แทนที่

View file

@ -10,7 +10,7 @@ new-terminal = 新建終端機
## Color schemes ## Color schemes
color-schemes = 配色方案 color-schemes = 配色方案
rename = 重命名 rename = 重新命名
export = 匯出 export = 匯出
delete = 刪除 delete = 刪除
import = 匯入 import = 匯入

View file

@ -566,7 +566,7 @@ pub enum Message {
ReorderTab(Pane, ReorderEvent), ReorderTab(Pane, ReorderEvent),
SaveOutput(Option<segmented_button::Entity>), SaveOutput(Option<segmented_button::Entity>),
SaveOutputResult(pane_grid::Pane, segmented_button::Entity, DialogResult), SaveOutputResult(pane_grid::Pane, segmented_button::Entity, DialogResult),
Surface(surface::Action), Surface(surface::Action<Message>),
SelectAll(Option<segmented_button::Entity>), SelectAll(Option<segmented_button::Entity>),
ShowAdvancedFontSettings(bool), ShowAdvancedFontSettings(bool),
ShowHeaderBar(bool), ShowHeaderBar(bool),
@ -604,7 +604,6 @@ pub enum Message {
ZoomIn, ZoomIn,
ZoomOut, ZoomOut,
ZoomReset, ZoomReset,
ContextMenuPopupClosed(window::Id),
} }
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -682,14 +681,6 @@ pub struct App {
shortcut_search_regex: Option<regex::Regex>, shortcut_search_regex: Option<regex::Regex>,
shortcut_search_value: String, shortcut_search_value: String,
modifiers: Modifiers, modifiers: Modifiers,
context_menu_popup: Option<(
window::Id,
pane_grid::Pane,
segmented_button::Entity,
Option<String>,
widget::Id,
cosmic::iced::Point,
)>,
#[cfg(feature = "password_manager")] #[cfg(feature = "password_manager")]
password_mgr: password_manager::PasswordManager, password_mgr: password_manager::PasswordManager,
} }
@ -2213,7 +2204,6 @@ impl Application for App {
shortcut_search_regex: None, shortcut_search_regex: None,
shortcut_search_value: String::new(), shortcut_search_value: String::new(),
modifiers: Modifiers::empty(), modifiers: Modifiers::empty(),
context_menu_popup: None,
#[cfg(feature = "password_manager")] #[cfg(feature = "password_manager")]
password_mgr: Default::default(), password_mgr: Default::default(),
}; };
@ -3433,16 +3423,7 @@ impl Application for App {
return self.update_title(None); return self.update_title(None);
} }
Message::TabContextAction(entity, action) => { Message::TabContextAction(entity, action) => {
// Close context menu popup
let mut tasks = Vec::new(); let mut tasks = Vec::new();
if let Some((_popup_id, _, _, _, _, _)) = self.context_menu_popup.take() {
#[cfg(feature = "wayland")]
if is_wayland() {
tasks.push(cosmic::task::message(Message::Surface(
cosmic::surface::action::destroy_popup(_popup_id),
)));
}
}
// Close terminal context menu state // Close terminal context menu state
if let Some(tab_model) = self.pane_model.active() if let Some(tab_model) = self.pane_model.active()
&& let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) && let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity)
@ -3451,11 +3432,8 @@ impl Application for App {
//Some actions need the menu_state, //Some actions need the menu_state,
//so only clear the position for them. //so only clear the position for them.
match action { match action {
Action::LaunchUrlByMenu | Action::CopyUrlByMenu => { // these read the link from the menu state and clear it themselves
if let Some(context_menu) = terminal.context_menu.as_mut() { Action::LaunchUrlByMenu | Action::CopyUrlByMenu => {}
context_menu.position = None;
}
}
_ => { _ => {
terminal.context_menu = None; terminal.context_menu = None;
} }
@ -3465,94 +3443,29 @@ impl Application for App {
return cosmic::Task::batch(tasks); return cosmic::Task::batch(tasks);
} }
Message::TabContextMenu(pane, menu_state) => { Message::TabContextMenu(pane, menu_state) => {
#[allow(unused_mut)]
let mut tasks = Vec::new();
// Close existing context menu popup if any
if let Some((_popup_id, _, _, _, _, _)) = self.context_menu_popup.take() {
#[cfg(feature = "wayland")]
if is_wayland() {
tasks.push(cosmic::task::message(Message::Surface(
cosmic::surface::action::destroy_popup(_popup_id),
)));
}
}
// Clear all terminal context_menu state // Clear all terminal context_menu state
for (_, tab_model) in self.pane_model.panes.iter() { for (_, tab_model) in self.pane_model.panes.iter() {
for entity in tab_model.iter() { for entity in tab_model.iter() {
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) { if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap(); let mut terminal = terminal.lock().unwrap();
terminal.context_menu = None; if terminal.context_menu.take().is_some() {
terminal.active_regex_match = None;
terminal.needs_update = true;
}
} }
} }
} }
if let Some(menu_state) = menu_state { // A right press records what is under the cursor
if let Some(_position) = menu_state.position { if let Some(menu_state) = menu_state
let local_position = menu_state.local_position.unwrap_or(_position); && let Some(tab_model) = self.pane_model.panes.get(pane)
if let Some(tab_model) = self.pane_model.panes.get(pane) { {
let entity = tab_model.active(); let entity = tab_model.active();
let link = menu_state.link.clone(); if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let popup_id = window::Id::unique(); terminal.lock().unwrap().context_menu = Some(menu_state);
if let Some(terminal) = tab_model.data::<Mutex<Terminal>>(entity) {
let mut terminal = terminal.lock().unwrap();
terminal.context_menu = Some(menu_state);
}
self.context_menu_popup = Some((
popup_id,
pane,
entity,
link,
widget::Id::unique(),
local_position,
));
#[cfg(feature = "wayland")]
if is_wayland() {
let main_window = self.core.main_window_id().unwrap();
let pos_x = _position.x as i32;
let pos_y = _position.y as i32;
tasks.push(cosmic::task::message(Message::Surface(
cosmic::surface::action::app_popup(
|_| Default::default(),
move |_app: &mut Self| {
use cosmic::cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
use cosmic::iced::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
SctkPopupSettings {
parent: main_window,
id: popup_id,
positioner: SctkPositioner {
size: None,
anchor_rect: cosmic::iced::Rectangle {
x: pos_x,
y: pos_y,
width: 1,
height: 1,
},
anchor: Anchor::None,
gravity: Gravity::BottomRight,
reactive: true,
..Default::default()
},
parent_size: None,
grab: true,
close_with_children: false,
input_zone: None,
}
}, None),
)));
}
}
} }
self.pane_model.set_focus(pane); self.pane_model.set_focus(pane);
} }
return cosmic::Task::batch(tasks);
} }
Message::TabNew => { Message::TabNew => {
// The profile from `--profile` only applies to the first tab // The profile from `--profile` only applies to the first tab
@ -3944,26 +3857,8 @@ impl Application for App {
self.reset_active_pane_zoom(); self.reset_active_pane_zoom();
return self.update_config(); return self.update_config();
} }
Message::ContextMenuPopupClosed(id) => {
if let Some((popup_id, pane, entity, _, _, _)) = &self.context_menu_popup
&& id == *popup_id
{
// Clear link underline on the terminal
if let Some(tab_model) = self.pane_model.panes.get(*pane)
&& let Some(terminal) = tab_model.data::<Mutex<Terminal>>(*entity)
{
let mut terminal = terminal.lock().unwrap();
terminal.context_menu = None;
terminal.active_regex_match = None;
terminal.needs_update = true;
}
self.context_menu_popup = None;
}
}
Message::Surface(a) => { Message::Surface(a) => {
return cosmic::task::message(cosmic::Action::Cosmic( return cosmic::task::message(cosmic::Action::Surface(a));
cosmic::app::Action::Surface(a),
));
} }
Message::ReorderTab( Message::ReorderTab(
pane, pane,
@ -4112,32 +4007,7 @@ impl Application for App {
] ]
} }
fn on_close_requested(&self, id: window::Id) -> Option<Self::Message> {
if let Some((popup_id, _, _, _, _, _)) = &self.context_menu_popup
&& id == *popup_id
{
return Some(Message::ContextMenuPopupClosed(id));
}
None
}
fn view_window(&self, window_id: window::Id) -> Element<'_, Message> { fn view_window(&self, window_id: window::Id) -> Element<'_, Message> {
if let Some((popup_id, pane, entity, ref link, ref autosize_id, _)) =
self.context_menu_popup
&& window_id == popup_id
{
return widget::autosize::autosize(
menu::context_menu(
&self.config,
&self.key_binds,
entity,
link.clone(),
self.terminal_monitors(pane, entity),
),
autosize_id.clone(),
)
.into();
}
match &self.dialog_opt { match &self.dialog_opt {
Some(dialog) => dialog.view(window_id), Some(dialog) => dialog.view(window_id),
None => widget::text("Unknown window ID").into(), None => widget::text("Unknown window ID").into(),
@ -4226,51 +4096,36 @@ impl Application for App {
terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane)); terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane));
} }
// If a context menu popup is active for this pane, inform the // The terminal records what was under the right press; the widget opens the
// terminal_box so it will emit on_context_menu(None) on click // menu on release, so build it from that state
// to dismiss the popup. let context_link = terminal
if self.context_menu_popup.is_some() { .lock()
terminal_box = terminal_box.context_menu(cosmic::iced::Point::ORIGIN); .unwrap()
.context_menu
.as_ref()
.map(|menu_state| menu_state.link.clone());
if context_link.is_some() {
terminal_box = terminal_box.context_menu_open(true);
} }
let mut context_menu = widget::context_menu(
let use_wayland_popup = { terminal_box,
#[cfg(feature = "wayland")] context_link.map(|link| {
{ menu::context_menu(
is_wayland() &self.config,
} &self.key_binds,
#[cfg(not(feature = "wayland"))] entity,
{ link,
false self.terminal_monitors(pane, entity),
} )
}; }),
)
let tab_element: Element<'_, Message> = if !use_wayland_popup { .item_width(widget::menu::ItemWidth::Uniform(360))
// Fallback: render context menu as an inline popover .on_close(Message::TabContextMenu(pane, None))
if let Some((_, popup_pane, popup_entity, ref link, _, point)) = .on_surface_action(Message::Surface);
self.context_menu_popup if let Some(window_id) = self.core.main_window_id() {
{ context_menu = context_menu.window_id(window_id);
if pane == popup_pane { }
let mut popover = widget::popover(terminal_box.context_menu(point)); tab_column = tab_column.push(context_menu);
popover = popover
.popup(menu::context_menu(
&self.config,
&self.key_binds,
popup_entity,
link.clone(),
self.terminal_monitors(popup_pane, popup_entity),
))
.position(widget::popover::Position::Point(point));
popover.into()
} else {
terminal_box.into()
}
} else {
terminal_box.into()
}
} else {
terminal_box.into()
};
tab_column = tab_column.push(tab_element);
} }
//Only draw find in the currently focused pane //Only draw find in the currently focused pane
@ -4451,14 +4306,6 @@ impl Application for App {
} }
} }
#[cfg(feature = "wayland")]
fn is_wayland() -> bool {
matches!(
cosmic::app::cosmic::windowing_system(),
Some(cosmic::app::cosmic::WindowingSystem::Wayland)
)
}
/// Divider color painted behind the pane grid to form pane borders. /// Divider color painted behind the pane grid to form pane borders.
/// ///
/// When blur is active the transparent container must be used, otherwise the /// When blur is active the transparent container must be used, otherwise the

View file

@ -1,17 +1,15 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use cosmic::iced::Point; use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::{Item as MenuItem, menu_button}; use cosmic::widget::menu::{Item as MenuItem, menu_button};
use cosmic::widget::space;
use cosmic::{ use cosmic::{
Element, Element,
app::Core, app::Core,
iced::core::Border, iced::core::Border,
iced::{Background, Length, advanced::widget::text::Style as TextStyle}, iced::{Background, Length},
theme,
widget::{ widget::{
self, divider, self,
menu::{ItemHeight, ItemWidth}, menu::{ItemHeight, ItemWidth},
responsive_menu_bar, segmented_button, responsive_menu_bar, segmented_button,
}, },
@ -23,147 +21,92 @@ use crate::{Action, ColorSchemeId, ColorSchemeKind, Config, Message, fl, termina
static MENU_ID: LazyLock<cosmic::widget::Id> = static MENU_ID: LazyLock<cosmic::widget::Id> =
LazyLock::new(|| cosmic::widget::Id::new("responsive-menu")); LazyLock::new(|| cosmic::widget::Id::new("responsive-menu"));
/// What the terminal found under the right-click
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MenuState { pub struct MenuState {
pub position: Option<Point>,
pub local_position: Option<Point>,
pub link: Option<String>, pub link: Option<String>,
} }
pub fn context_menu<'a>( /// A context menu action dispatched to the tab the menu was opened on.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TabAction(pub segmented_button::Entity, pub Action);
impl MenuAction for TabAction {
type Message = Message;
fn message(&self) -> Message {
Message::TabContextAction(self.0, self.1)
}
}
pub fn context_menu(
config: &Config, config: &Config,
key_binds: &HashMap<KeyBind, Action>, key_binds: &HashMap<KeyBind, Action>,
entity: segmented_button::Entity, entity: segmented_button::Entity,
link: Option<String>, link: Option<String>,
monitors: (bool, bool, bool), monitors: (bool, bool, bool),
) -> Element<'a, Message> { ) -> Vec<widget::menu::Tree<Message>> {
let find_key = |action: &Action| -> String { let item =
for (key_bind, key_action) in key_binds { |label: String, action: Action| MenuItem::Button(label, None, TabAction(entity, action));
if action == key_action { let check = |label: String, value: bool, action: Action| {
return key_bind.to_string(); MenuItem::CheckBox(label, None, value, TabAction(entity, action))
}
}
String::new()
};
fn key_style(theme: &cosmic::Theme) -> TextStyle {
let mut color = theme.cosmic().background(theme.transparent).component.on;
color.alpha *= 0.75;
TextStyle {
color: Some(color.into()),
..Default::default()
}
}
let menu_item = |label, action| {
let key = find_key(&action);
menu_button(vec![
widget::text(label).into(),
space::horizontal().into(),
widget::text(key)
.class(theme::Text::Custom(key_style))
.into(),
])
.on_press(Message::TabContextAction(entity, action))
}; };
let menu_checkbox = |label, value, action| { let mut items = vec![
menu_button(vec![ item(fl!("new-tab"), Action::TabNew),
widget::text(label).into(), item(fl!("rename-tab"), Action::TabRename),
widget::space::horizontal().into(), MenuItem::Divider,
widget::toggler(value) item(fl!("copy"), Action::Copy),
.on_toggle(move |_| Message::TabContextAction(entity, action)) item(fl!("paste"), Action::Paste),
.size(16.0) item(fl!("select-all"), Action::SelectAll),
.into(), MenuItem::Divider,
]) item(fl!("clear-scrollback"), Action::ClearScrollback),
.on_press(Message::TabContextAction(entity, action)) item(fl!("save-output"), Action::SaveOutput),
}; MenuItem::Divider,
check(
let mut rows = vec![
Element::from(menu_item(fl!("copy"), Action::Copy)),
Element::from(menu_item(fl!("paste"), Action::Paste)),
Element::from(menu_item(fl!("select-all"), Action::SelectAll)),
Element::from(divider::horizontal::light()),
Element::from(menu_item(fl!("clear-scrollback"), Action::ClearScrollback)),
Element::from(menu_item(fl!("save-output"), Action::SaveOutput)),
Element::from(divider::horizontal::light()),
Element::from(menu_checkbox(
fl!("monitor-activity"), fl!("monitor-activity"),
monitors.0, monitors.0,
Action::Monitor(MonitorKind::Activity), Action::Monitor(MonitorKind::Activity),
)), ),
Element::from(menu_checkbox( check(
fl!("monitor-silence"), fl!("monitor-silence"),
monitors.1, monitors.1,
Action::Monitor(MonitorKind::Silence), Action::Monitor(MonitorKind::Silence),
)), ),
Element::from(menu_checkbox( check(
fl!("monitor-process-finished"), fl!("monitor-process-finished"),
monitors.2, monitors.2,
Action::Monitor(MonitorKind::ProcessFinished), Action::Monitor(MonitorKind::ProcessFinished),
)), ),
Element::from(divider::horizontal::light()), MenuItem::Divider,
Element::from(menu_item( item(fl!("split-horizontal"), Action::PaneSplitHorizontal),
fl!("split-horizontal"), item(fl!("split-vertical"), Action::PaneSplitVertical),
Action::PaneSplitHorizontal, item(fl!("pane-toggle-maximize"), Action::PaneToggleMaximized),
)), MenuItem::Divider,
Element::from(menu_item(fl!("split-vertical"), Action::PaneSplitVertical)), MenuItem::CheckBox(
Element::from(menu_item( fl!("window-header"),
fl!("pane-toggle-maximize"), None,
Action::PaneToggleMaximized, config.show_headerbar,
)), TabAction(entity, Action::ShowHeaderBar(!config.show_headerbar)),
Element::from(divider::horizontal::light()), ),
Element::from(menu_item(fl!("new-tab"), Action::TabNew)), MenuItem::Divider,
Element::from(menu_item(fl!("rename-tab"), Action::TabRename)),
Element::from(menu_item(fl!("menu-settings"), Action::Settings)),
Element::from(menu_item(fl!("menu-about"), Action::About)),
]; ];
#[cfg(feature = "password_manager")] #[cfg(feature = "password_manager")]
{ items.push(item(fl!("menu-password-manager"), Action::PasswordManager));
rows.push(Element::from(menu_item( items.push(item(fl!("menu-settings"), Action::Settings));
fl!("menu-password-manager"), items.push(item(fl!("menu-about"), Action::About));
Action::PasswordManager, // If we have a link, prepend the link items
)));
}
rows.push(Element::from(menu_checkbox(
fl!("show-headerbar"),
config.show_headerbar,
Action::ShowHeaderBar(!config.show_headerbar),
)));
//If we have a link
//prepend the Open Link item
if link.is_some() { if link.is_some() {
rows.insert( items.insert(0, item(fl!("open-link"), Action::LaunchUrlByMenu));
0, items.insert(1, item(fl!("copy-link"), Action::CopyUrlByMenu));
Element::from(menu_item(fl!("open-link"), Action::LaunchUrlByMenu)), items.insert(2, MenuItem::Divider);
);
rows.insert(
1,
Element::from(menu_item(fl!("copy-link"), Action::CopyUrlByMenu)),
);
rows.insert(2, Element::from(divider::horizontal::light()));
} }
let content = widget::menu::menu_column::MenuColumn::with_children(rows);
widget::container(content) let key_binds: HashMap<KeyBind, TabAction> = key_binds
.padding(1) .iter()
//TODO: move style to libcosmic .map(|(key_bind, action)| (key_bind.clone(), TabAction(entity, *action)))
.style(|theme| { .collect();
let cosmic = theme.cosmic(); widget::menu::items(&key_binds, items)
let component = &cosmic.background(theme.transparent).component;
widget::container::Style {
icon_color: Some(component.on.into()),
text_color: Some(component.on.into()),
background: Some(Background::Color(component.base.into())),
border: Border {
radius: cosmic.radius_s().map(|x| x + 1.0).into(),
width: 1.0,
color: component.divider.into(),
},
..Default::default()
}
})
.width(Length::Fixed(360.0))
.into()
} }
pub fn color_scheme_menu<'a>( pub fn color_scheme_menu<'a>(

View file

@ -218,11 +218,17 @@ impl TerminalPaneGrid {
} }
} }
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Metadata { pub struct Metadata {
pub bg: cosmic_text::Color, pub bg: cosmic_text::Color,
pub underline_color: cosmic_text::Color, pub underline_color: cosmic_text::Color,
pub flags: Flags, pub flags: Flags,
/// Remplissage natif (élément de bloc Unicode) — voir [`BlockFill`].
pub block: Option<BlockFill>,
/// Couleur du remplissage natif. Hors bloc, sans effet ; participant à
/// l'égalité pour que deux blocs adjacents de couleurs différentes ne
/// fusionnent pas en un seul quad.
pub block_color: cosmic_text::Color,
} }
impl Metadata { impl Metadata {
@ -232,6 +238,8 @@ impl Metadata {
bg, bg,
underline_color, underline_color,
flags, flags,
block: None,
block_color: underline_color,
} }
} }
@ -245,6 +253,73 @@ impl Metadata {
fn with_flags(self, flags: Flags) -> Self { fn with_flags(self, flags: Flags) -> Self {
Self { flags, ..self } Self { flags, ..self }
} }
fn with_block(mut self, block: Option<BlockFill>, color: cosmic_text::Color) -> Self {
self.block = block;
self.block_color = color;
self
}
}
/// Élément de bloc Unicode (U+2580–U+259F) rendu nativement.
///
/// Ces caractères ne passent PAS par le glyphe de la police : le `█` d'une
/// police couvre à peine l'em (≈ 1× la taille de police) alors que la cellule
/// fait `line_height` (≈ 1,4×) — d'où des trous entre les rangées et un rendu
/// crénelé. À la place, chaque bloc est remplacé par une espace (le glyphe
/// disparaît) et peint en quad GPU couvrant exactement la fraction voulue de
/// la cellule : bords alignés sur la grille, jointif par construction, net à
/// toute échelle. C'est l'approche de kitty/WezTerm.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BlockFill {
/// Un ou deux rectangles en huitièmes de cellule : `(x0, x1, y0, y1)`,
/// abscisse et ordonnée depuis le coin haut-gauche. Un rectangle vide
/// (`x0 >= x1` ou `y0 >= y1`) est ignoré au tracé — deux rectangles
/// suffisent pour toute la famille des quadrants.
Rects([(u8, u8, u8, u8); 2]),
/// Trame (`░▒▓`) : rectangle plein, alpha en quarts (1–3).
Shade(u8),
}
/// Mappe un caractère vers son remplissage natif, s'il est un élément de bloc.
pub fn block_fill(c: char) -> Option<BlockFill> {
use BlockFill::{Rects, Shade};
const NONE: (u8, u8, u8, u8) = (8, 8, 8, 8);
match c {
'▀' => Some(Rects([(0, 8, 0, 4), NONE])),
'▁' => Some(Rects([(0, 8, 7, 8), NONE])),
'▂' => Some(Rects([(0, 8, 6, 8), NONE])),
'▃' => Some(Rects([(0, 8, 5, 8), NONE])),
'▄' => Some(Rects([(0, 8, 4, 8), NONE])),
'▅' => Some(Rects([(0, 8, 3, 8), NONE])),
'▆' => Some(Rects([(0, 8, 2, 8), NONE])),
'▇' => Some(Rects([(0, 8, 1, 8), NONE])),
'█' => Some(Rects([(0, 8, 0, 8), NONE])),
'▉' => Some(Rects([(0, 7, 0, 8), NONE])),
'▊' => Some(Rects([(0, 6, 0, 8), NONE])),
'▋' => Some(Rects([(0, 5, 0, 8), NONE])),
'▌' => Some(Rects([(0, 4, 0, 8), NONE])),
'▍' => Some(Rects([(0, 3, 0, 8), NONE])),
'▎' => Some(Rects([(0, 2, 0, 8), NONE])),
'▏' => Some(Rects([(0, 1, 0, 8), NONE])),
'▐' => Some(Rects([(4, 8, 0, 8), NONE])),
'░' => Some(Shade(1)),
'▒' => Some(Shade(2)),
'▓' => Some(Shade(3)),
'▔' => Some(Rects([(0, 8, 0, 1), NONE])),
'▕' => Some(Rects([(7, 8, 0, 8), NONE])),
'▖' => Some(Rects([(0, 4, 4, 8), NONE])),
'▗' => Some(Rects([(4, 8, 4, 8), NONE])),
'▘' => Some(Rects([(0, 4, 0, 4), NONE])),
'▝' => Some(Rects([(4, 8, 0, 4), NONE])),
'▙' => Some(Rects([(0, 4, 0, 8), (4, 8, 4, 8)])),
'▚' => Some(Rects([(0, 4, 0, 4), (4, 8, 4, 8)])),
'▛' => Some(Rects([(0, 8, 0, 4), (0, 4, 4, 8)])),
'▜' => Some(Rects([(0, 8, 0, 4), (4, 8, 4, 8)])),
'▞' => Some(Rects([(4, 8, 0, 4), (0, 4, 4, 8)])),
'▟' => Some(Rects([(0, 8, 4, 8), (4, 8, 0, 4)])),
_ => None,
}
} }
pub struct Terminal { pub struct Terminal {
@ -905,9 +980,14 @@ impl Terminal {
} }
let start = text.len(); let start = text.len();
// Tab skip/stop is handled by alacritty_terminal // Tab skip/stop is handled by alacritty_terminal.
// Les éléments de bloc ne deviennent pas des glyphes : la
// police ne couvre pas la cellule (trous d'interligne) ;
// ils sont peints en quads natifs au tracé, via Metadata.
let block = block_fill(indexed.cell.c);
text.push(match indexed.cell.c { text.push(match indexed.cell.c {
'\t' => ' ', '\t' => ' ',
c if block.is_some() => ' ',
c => c, c => c,
}); });
if let Some(zerowidth) = indexed.cell.zerowidth() { if let Some(zerowidth) = indexed.cell.zerowidth() {
@ -1025,6 +1105,7 @@ impl Terminal {
} }
let metadata = Metadata::new(bg, fg) let metadata = Metadata::new(bg, fg)
.with_block(block, fg)
.with_flags(flags) .with_flags(flags)
.with_underline_color(underline_color); .with_underline_color(underline_color);
let (meta_idx, _) = self.metadata_set.insert_full(metadata); let (meta_idx, _) = self.metadata_set.insert_full(metadata);
@ -1344,3 +1425,53 @@ impl Drop for Terminal {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_fill_couvre_toute_la_plage_unicode() {
// U+2580–U+259F : chaque élément de bloc a sa géométrie native.
for code in 0x2580..=0x259F {
let c = char::from_u32(code).unwrap();
assert!(
block_fill(c).is_some(),
"{c} (U+{code:04X}) sans remplissage"
);
}
// Hors de la plage : filets, espaces, lettres — pas de remplissage.
for c in [' ', '─', '│', 'M', '●', '\u{25A0}'] {
assert!(
block_fill(c).is_none(),
"{c} ne doit pas être un bloc natif"
);
}
}
#[test]
fn block_fill_geometry_toujours_valide() {
// Fractions croissantes, bornées à la cellule — sinon le quad
// déborderait sur la cellule voisine ou serait rejeté au tracé.
for c in (char::from_u32(0x2580).unwrap()..=char::from_u32(0x259F).unwrap())
.chain(['░', '▒', '▓'].iter().copied())
{
let Some(fill) = block_fill(c) else { continue };
match fill {
BlockFill::Rects(rects) => {
for (x0, x1, y0, y1) in rects {
// Rectangle vide (sentinelle) ou borné à la cellule.
let empty = x0 >= x1 || y0 >= y1;
assert!(
empty || (x1 <= 8 && y1 <= 8),
"{c}: géométrie déborde la cellule"
);
}
}
BlockFill::Shade(quarters) => {
assert!((1..=3).contains(&quarters), "{c}: alpha de trame invalide")
}
}
}
}
}

View file

@ -46,7 +46,11 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use crate::{Action, Terminal, TerminalScroll, menu::MenuState, terminal::Metadata}; use crate::{
Action, Terminal, TerminalScroll,
menu::MenuState,
terminal::{BlockFill, Metadata},
};
const AUTOSCROLL_INTERVAL: Duration = Duration::from_millis(100); const AUTOSCROLL_INTERVAL: Duration = Duration::from_millis(100);
@ -112,7 +116,7 @@ pub struct TerminalBox<'a, Message> {
show_headerbar: bool, show_headerbar: bool,
pane_border_radius: Option<Radius>, pane_border_radius: Option<Radius>,
click_timing: Duration, click_timing: Duration,
context_menu: Option<Point>, context_menu_open: bool,
on_context_menu: Option<Box<dyn Fn(Option<MenuState>) -> Message + 'a>>, on_context_menu: Option<Box<dyn Fn(Option<MenuState>) -> Message + 'a>>,
on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>, on_mouse_enter: Option<Box<dyn Fn() -> Message + 'a>>,
opacity: Option<f32>, opacity: Option<f32>,
@ -139,7 +143,7 @@ where
show_headerbar: true, show_headerbar: true,
pane_border_radius: None, pane_border_radius: None,
click_timing: Duration::from_millis(500), click_timing: Duration::from_millis(500),
context_menu: None, context_menu_open: false,
on_context_menu: None, on_context_menu: None,
on_mouse_enter: None, on_mouse_enter: None,
opacity: None, opacity: None,
@ -184,8 +188,8 @@ where
self self
} }
pub fn context_menu(mut self, position: Point) -> Self { pub fn context_menu_open(mut self, open: bool) -> Self {
self.context_menu = Some(position); self.context_menu_open = open;
self self
} }
@ -249,7 +253,7 @@ where
if !state.is_focused { if !state.is_focused {
return InputMethod::Disabled; return InputMethod::Disabled;
} }
if self.context_menu.is_some() { if self.context_menu_open {
return InputMethod::Disabled; return InputMethod::Disabled;
} }
@ -590,6 +594,41 @@ where
); );
} }
// Éléments de bloc peints nativement : fractions de la
// cellule (ou de la course fusionnée, à géométrie
// séparable), bords calés sur la grille — zéro glyphe,
// zéro interligne.
if let Some(block) = metadata.block {
let span_w = self.end_x - self.start_x;
let mut color = cosmic_text_to_iced_color(metadata.block_color);
let rects = match block {
BlockFill::Rects(rects) => rects,
BlockFill::Shade(quarters) => {
color.a *= f32::from(quarters) / 4.0;
[(0, 8, 0, 8), (8, 8, 8, 8)]
}
};
for (x0, x1, y0, y1) in rects {
if x0 >= x1 || y0 >= y1 {
continue;
}
let left = self.start_x + span_w * f32::from(x0) / 8.0;
let right = self.start_x + span_w * f32::from(x1) / 8.0;
let top = self.line_top + self.line_height * f32::from(y0) / 8.0;
let bottom = self.line_top + self.line_height * f32::from(y1) / 8.0;
renderer.fill_quad(
Quad {
bounds: Rectangle::new(
self.view_position + Vector::new(left.floor(), top),
Size::new(right.ceil() - left.floor(), bottom - top),
),
..Default::default()
},
color,
);
}
}
if !metadata.flags.is_empty() { if !metadata.flags.is_empty() {
let style_line_height = (self.glyph_font_size / 10.0).clamp(1.0, 16.0); let style_line_height = (self.glyph_font_size / 10.0).clamp(1.0, 16.0);
@ -1353,11 +1392,11 @@ where
} }
// Update context menu state // Update context menu state
if let Some(on_context_menu) = &self.on_context_menu { if let Some(on_context_menu) = &self.on_context_menu {
match self.context_menu { match self.context_menu_open {
Some(_) => { true => {
shell.publish(on_context_menu(None)); shell.publish(on_context_menu(None));
} }
None => { false => {
if *button == Button::Right { if *button == Button::Right {
let x = p.x - self.padding.left; let x = p.x - self.padding.left;
let y = p.y - self.padding.top; let y = p.y - self.padding.top;
@ -1375,15 +1414,7 @@ where
None, None,
); );
let link = get_hyperlink(&terminal, location); let link = get_hyperlink(&terminal, location);
let abs = cosmic::iced::Point::new( shell.publish(on_context_menu(Some(MenuState { link })));
layout.bounds().x + p.x,
layout.bounds().y + p.y,
);
shell.publish(on_context_menu(Some(MenuState {
position: Some(abs),
local_position: Some(p),
link,
})));
} }
} }
} }