logiguard fork: GPUI xdg-activation keyboard-focus serial fix
Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled

Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree
with a 3-file patch applied (no upstream history).

Patch (crates/gpui_linux/src/linux/wayland/):
  - serial.rs: add SerialKind::KeyboardEnter
  - client.rs: store wl_keyboard.enter serial; add latest_serial_of()
  - window.rs: activate() uses keyboard-enter serial (Mutter focus gate)

Mutter honors window activation only when the token carries the keyboard-
focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial.
See docs/tray-window-focus-wayland.md in logiguard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mohamad Khani
2026-07-14 02:22:17 +03:30
commit b72a46db68
3984 changed files with 1583326 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
[package]
name = "encoding_selector"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
license = "GPL-3.0-or-later"
[lints]
workspace = true
[lib]
path = "src/encoding_selector.rs"
doctest = false
[dependencies]
editor.workspace = true
encoding_rs.workspace = true
fuzzy.workspace = true
gpui.workspace = true
language.workspace = true
picker.workspace = true
project.workspace = true
ui.workspace = true
util.workspace = true
workspace.workspace = true

View File

@@ -0,0 +1 @@
../../LICENSE-GPL

View File

@@ -0,0 +1,143 @@
use crate::{EncodingSelector, Toggle};
use editor::Editor;
use encoding_rs::{Encoding, UTF_8};
use gpui::{
App, Context, Entity, IntoElement, ParentElement, Render, Styled, Subscription, WeakEntity,
Window, div,
};
use project::Project;
use ui::{Button, ButtonCommon, Clickable, LabelSize, Tooltip};
use workspace::{
EncodingDisplayOptions, HideStatusItem, StatusBarSettings, StatusItemView, Workspace,
item::{ItemHandle, Settings},
};
pub struct ActiveBufferEncoding {
active_encoding: Option<&'static Encoding>,
workspace: WeakEntity<Workspace>,
project: Entity<Project>,
_observe_active_editor: Option<Subscription>,
has_bom: bool,
is_dirty: bool,
is_shared: bool,
is_via_remote_server: bool,
}
impl ActiveBufferEncoding {
pub fn new(workspace: &Workspace) -> Self {
Self {
active_encoding: None,
workspace: workspace.weak_handle(),
project: workspace.project().clone(),
_observe_active_editor: None,
has_bom: false,
is_dirty: false,
is_shared: false,
is_via_remote_server: false,
}
}
fn update_encoding(&mut self, editor: Entity<Editor>, _: &mut Window, cx: &mut Context<Self>) {
self.active_encoding = None;
self.has_bom = false;
self.is_dirty = false;
let project = self.project.read(cx);
self.is_shared = project.is_shared();
self.is_via_remote_server = project.is_via_remote_server();
if let Some(buffer) = editor.read(cx).active_buffer(cx) {
let buffer = buffer.read(cx);
self.active_encoding = Some(buffer.encoding());
self.has_bom = buffer.has_bom();
self.is_dirty = buffer.is_dirty();
}
cx.notify();
}
}
impl Render for ActiveBufferEncoding {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let Some(active_encoding) = self.active_encoding else {
return div().hidden();
};
let display_option = StatusBarSettings::get_global(cx).active_encoding_button;
let is_utf8 = active_encoding == UTF_8;
if !display_option.should_show(is_utf8, self.has_bom) {
return div().hidden();
}
let mut text = active_encoding.name().to_string();
if self.has_bom {
text.push_str(" (BOM)");
}
let (disabled, tooltip_text) = if self.is_dirty {
(true, "Save file to change encoding")
} else if self.is_shared {
(true, "Cannot change encoding during collaboration")
} else if self.is_via_remote_server {
(true, "Cannot change encoding of remote server file")
} else {
(false, "Reopen with Encoding")
};
div().child(
Button::new("change-encoding", text)
.label_size(LabelSize::Small)
.on_click(cx.listener(move |this, _, window, cx| {
if disabled {
return;
}
if let Some(workspace) = this.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
EncodingSelector::toggle(workspace, window, cx)
});
}
}))
.tooltip(move |_window, cx| {
if disabled {
Tooltip::text(tooltip_text)(_window, cx)
} else {
Tooltip::for_action(tooltip_text, &Toggle, cx)
}
}),
)
}
}
impl StatusItemView for ActiveBufferEncoding {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.downcast::<Editor>()) {
self._observe_active_editor =
Some(cx.observe_in(&editor, window, Self::update_encoding));
self.update_encoding(editor, window, cx);
} else {
self.active_encoding = None;
self.has_bom = false;
self.is_dirty = false;
self.is_shared = false;
self.is_via_remote_server = false;
self._observe_active_editor = None;
}
cx.notify();
}
fn hide_setting(&self, _: &App) -> Option<HideStatusItem> {
Some(HideStatusItem::new(|settings| {
settings
.status_bar
.get_or_insert_default()
.active_encoding_button = Some(EncodingDisplayOptions::Disabled);
}))
}
}

View File

@@ -0,0 +1,327 @@
mod active_buffer_encoding;
pub use active_buffer_encoding::ActiveBufferEncoding;
use editor::Editor;
use encoding_rs::Encoding;
use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
use gpui::{
App, AppContext, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
InteractiveElement, ParentElement, Render, Styled, Task, WeakEntity, Window, actions,
};
use language::Buffer;
use picker::{Picker, PickerDelegate};
use std::sync::Arc;
use ui::{HighlightedLabel, ListItem, ListItemSpacing, Toggleable, v_flex};
use util::ResultExt;
use workspace::{ModalView, Toast, Workspace, notifications::NotificationId};
actions!(
encoding_selector,
[
/// Toggles the encoding selector modal.
Toggle
]
);
pub fn init(cx: &mut App) {
cx.observe_new(EncodingSelector::register).detach();
}
pub struct EncodingSelector {
picker: Entity<Picker<EncodingSelectorDelegate>>,
}
impl EncodingSelector {
fn register(
workspace: &mut Workspace,
_window: Option<&mut Window>,
_: &mut Context<Workspace>,
) {
workspace.register_action(move |workspace, _: &Toggle, window, cx| {
Self::toggle(workspace, window, cx);
});
}
pub fn toggle(
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Option<()> {
let buffer = workspace
.active_item(cx)?
.act_as::<Editor>(cx)?
.read(cx)
.active_buffer(cx)?;
let buffer_handle = buffer.read(cx);
let project = workspace.project().read(cx);
if buffer_handle.is_dirty() {
workspace.show_toast(
Toast::new(
NotificationId::unique::<EncodingSelector>(),
"Save file to change encoding",
),
cx,
);
return Some(());
}
if project.is_shared() {
workspace.show_toast(
Toast::new(
NotificationId::unique::<EncodingSelector>(),
"Cannot change encoding during collaboration",
),
cx,
);
return Some(());
}
if project.is_via_remote_server() {
workspace.show_toast(
Toast::new(
NotificationId::unique::<EncodingSelector>(),
"Cannot change encoding of remote server file",
),
cx,
);
return Some(());
}
workspace.toggle_modal(window, cx, move |window, cx| {
EncodingSelector::new(buffer, window, cx)
});
Some(())
}
fn new(buffer: Entity<Buffer>, window: &mut Window, cx: &mut Context<Self>) -> Self {
let delegate = EncodingSelectorDelegate::new(cx.entity().downgrade(), buffer);
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx));
Self { picker }
}
}
impl Render for EncodingSelector {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl gpui::IntoElement {
v_flex()
.key_context("EncodingSelector")
.w(gpui::rems(34.))
.child(self.picker.clone())
}
}
impl Focusable for EncodingSelector {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.picker.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for EncodingSelector {}
impl ModalView for EncodingSelector {}
pub struct EncodingSelectorDelegate {
encoding_selector: WeakEntity<EncodingSelector>,
buffer: Entity<Buffer>,
encodings: Vec<&'static Encoding>,
match_candidates: Arc<Vec<StringMatchCandidate>>,
matches: Vec<StringMatch>,
selected_index: usize,
}
impl EncodingSelectorDelegate {
fn new(encoding_selector: WeakEntity<EncodingSelector>, buffer: Entity<Buffer>) -> Self {
let encodings = available_encodings();
let match_candidates = encodings
.iter()
.enumerate()
.map(|(id, enc)| StringMatchCandidate::new(id, enc.name()))
.collect::<Vec<_>>();
Self {
encoding_selector,
buffer,
encodings,
match_candidates: Arc::new(match_candidates),
matches: vec![],
selected_index: 0,
}
}
fn render_data_for_match(&self, mat: &StringMatch, cx: &App) -> String {
let candidate_encoding = self.encodings[mat.candidate_id];
let current_encoding = self.buffer.read(cx).encoding();
if candidate_encoding.name() == current_encoding.name() {
format!("{} (current)", candidate_encoding.name())
} else {
candidate_encoding.name().to_string()
}
}
}
fn available_encodings() -> Vec<&'static Encoding> {
let mut encodings = vec![
// Unicode
encoding_rs::UTF_8,
encoding_rs::UTF_16LE,
encoding_rs::UTF_16BE,
// Japanese
encoding_rs::SHIFT_JIS,
encoding_rs::EUC_JP,
encoding_rs::ISO_2022_JP,
// Chinese
encoding_rs::GBK,
encoding_rs::GB18030,
encoding_rs::BIG5,
// Korean
encoding_rs::EUC_KR,
// Windows / Single Byte Series
encoding_rs::WINDOWS_1252, // Western (ISO-8859-1 unified)
encoding_rs::WINDOWS_1250, // Central European
encoding_rs::WINDOWS_1251, // Cyrillic
encoding_rs::WINDOWS_1253, // Greek
encoding_rs::WINDOWS_1254, // Turkish (ISO-8859-9 unified)
encoding_rs::WINDOWS_1255, // Hebrew
encoding_rs::WINDOWS_1256, // Arabic
encoding_rs::WINDOWS_1257, // Baltic
encoding_rs::WINDOWS_1258, // Vietnamese
encoding_rs::WINDOWS_874, // Thai
// ISO-8859 Series (others)
encoding_rs::ISO_8859_2,
encoding_rs::ISO_8859_3,
encoding_rs::ISO_8859_4,
encoding_rs::ISO_8859_5,
encoding_rs::ISO_8859_6,
encoding_rs::ISO_8859_7,
encoding_rs::ISO_8859_8,
encoding_rs::ISO_8859_8_I, // Logical Hebrew
encoding_rs::ISO_8859_10,
encoding_rs::ISO_8859_13,
encoding_rs::ISO_8859_14,
encoding_rs::ISO_8859_15,
encoding_rs::ISO_8859_16,
// Cyrillic / Legacy Misc
encoding_rs::KOI8_R,
encoding_rs::KOI8_U,
encoding_rs::IBM866,
encoding_rs::MACINTOSH,
encoding_rs::X_MAC_CYRILLIC,
// NOTE: The following encodings are intentionally excluded from the list:
//
// 1. encoding_rs::REPLACEMENT
// Used internally for decoding errors. Not suitable for user selection.
//
// 2. encoding_rs::X_USER_DEFINED
// Used for binary data emulation (legacy web behavior). Not for general text editing.
];
encodings.sort_by_key(|enc| enc.name());
encodings
}
impl PickerDelegate for EncodingSelectorDelegate {
type ListItem = ListItem;
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
"Reopen with encoding...".into()
}
fn match_count(&self) -> usize {
self.matches.len()
}
fn selected_index(&self) -> usize {
self.selected_index
}
fn set_selected_index(
&mut self,
ix: usize,
_window: &mut Window,
_: &mut Context<Picker<Self>>,
) {
self.selected_index = ix;
}
fn update_matches(
&mut self,
query: String,
window: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Task<()> {
let background = cx.background_executor().clone();
let candidates = self.match_candidates.clone();
cx.spawn_in(window, async move |this, cx| {
let matches = if query.is_empty() {
candidates
.iter()
.enumerate()
.map(|(index, candidate)| StringMatch {
candidate_id: index,
string: candidate.string.clone(),
positions: Vec::new(),
score: 0.0,
})
.collect()
} else {
match_strings(
&candidates,
&query,
false,
true,
100,
&Default::default(),
background,
)
.await
};
this.update(cx, |this, cx| {
let delegate = &mut this.delegate;
delegate.matches = matches;
delegate.selected_index = delegate
.selected_index
.min(delegate.matches.len().saturating_sub(1));
cx.notify();
})
.log_err();
})
}
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
if let Some(mat) = self.matches.get(self.selected_index) {
let selected_encoding = self.encodings[mat.candidate_id];
self.buffer.update(cx, |buffer, cx| {
let _ = buffer.reload_with_encoding(selected_encoding, cx);
});
}
self.dismissed(window, cx);
}
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
self.encoding_selector
.update(cx, |_, cx| cx.emit(DismissEvent))
.log_err();
}
fn render_match(
&self,
ix: usize,
selected: bool,
_: &mut Window,
cx: &mut Context<Picker<Self>>,
) -> Option<Self::ListItem> {
let mat = &self.matches.get(ix)?;
let label = self.render_data_for_match(mat, cx);
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.toggle_state(selected)
.child(HighlightedLabel::new(label, mat.positions.clone())),
)
}
}