Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,11 @@ discovery and is the preferred way to isolate catalogue tests.
manual-selection precedence when changing search or cache behavior.
- Relm4 initializes GTK; do not call `gtk::init()`.
- The application is a Wayland layer-shell overlay and is not expected to run on
X11 or without compositor layer-shell support.
X11. When `gtk4_layer_shell::is_supported()` is false (GNOME's Mutter), the
overlay falls back to `DragMode::ToplevelWindow`: a plain undecorated window
the compositor moves, with no click-through input region, edge snapping, or
remembered position. Keep layer-shell calls and margin updates behind
`uses_layer_shell()` / `uses_layer_margins()` so that path never reaches them.
- Before GTK initialization, `src/lib.rs` supplies default `GSK_RENDERER=gl` and
`GTK_A11Y=none` values when unset. Preserve caller-provided values.

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ FloatLyrics 是一款面向 Linux Wayland 的 MPRIS 悬浮歌词应用。它会

| 组件 | 要求 |
|---|---|
| 桌面会话 | Linux Wayland,合成器支持 layer-shell |
| 桌面会话 | Linux Wayland;悬浮覆盖层需要合成器支持 layer-shell,否则退化为普通窗口 |
| 播放器 | 提供 `PlaybackStatus`、`Position` 和含标题 `Metadata` 的 MPRIS 播放器 |
| 运行库 | GTK 4.12 或更高版本、gtk4-layer-shell、WebKitGTK 6.0 |

FloatLyrics 依赖 layer-shell,目前不支持 X11。可运行 `echo "$XDG_SESSION_TYPE"` 检查当前会话类型。
FloatLyrics 依赖 layer-shell 实现悬浮覆盖层,目前不支持 X11;X11 下同样会退化为普通窗口,但不在支持范围内。可运行 `echo "$XDG_SESSION_TYPE"` 检查当前会话类型。

已知兼容的合成器包括 GNOME(Mutter)、KDE(KWin)、Hyprland 和 Sway;其他支持 layer-shell 的合成器也可能正常工作。
KDE(KWin)、Hyprland 和 Sway 等合成器原生支持 layer-shell;GNOME(Mutter)不支持该协议,浮窗会退化为可拖拽的普通窗口,点击穿透、屏幕吸附与记住窗口位置不可用;可在窗口上按 `Alt+Space` 打开“置顶”与“在所有工作区显示”

## 安装

Expand Down Expand Up @@ -269,7 +269,7 @@ enabled = true

## 常见问题与限制

- **浮窗没有出现:** 确认当前是 Wayland 会话、合成器支持 layer-shell,并且播放器正在通过 MPRIS 暴露播放状态。
- **浮窗没有出现:** 确认当前是 Wayland 会话,并且播放器正在通过 MPRIS 暴露播放状态。
- **播放器没有被识别:** 运行 `busctl --user list` 检查 `org.mpris.MediaPlayer2.*` 名称,必要时调整 `player.preferred_players` 或 `player.ignored_players`。
- **歌词时间不准:** 使用浮窗按钮校准当前歌曲,或在设置中调整全局偏移 `lyrics.offset_ms`。播放器未正确更新 `Position` 或 `Metadata` 时可能无法可靠同步。
- **逐词高亮与演唱不完全一致:** 逐词时间只取自歌词源自己的逐字数据(AMLL TTML DB 的逐词 TTML、QQ 音乐 QRC、网易云逐字、汽水 KRC);只提供整行时间的歌词源(例如 LRC)不做逐词高亮,整行一起点亮。网易云的逐字歌词是它单独一份时间表,FloatLyrics 只取其中的时间、文字仍用它的整行版本,对不上的行(例如逐字版给某个词打了码)同样退回整行高亮。
Expand Down
1 change: 1 addition & 0 deletions data/locale/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"SearchingProviders": "Searching lyrics sources…",
"SelectCandidatePreview": "Select a candidate to preview its lyrics",
"SettingsWindowTitle": "FloatLyrics Settings",
"OverlayWindowTitle": "FloatLyrics Lyrics",
"ShowRomanization": "Show romanization",
"ShowRomanizationDescription": "Generated locally from the original lyrics",
"ChineseRomanization": "Chinese pronunciation",
Expand Down
1 change: 1 addition & 0 deletions data/locale/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"SearchingProviders": "正在搜索歌词源…",
"SelectCandidatePreview": "选择候选歌词后将在这里预览",
"SettingsWindowTitle": "FloatLyrics 设置",
"OverlayWindowTitle": "FloatLyrics 歌词",
"ShowRomanization": "显示罗马音",
"ShowRomanizationDescription": "始终根据原歌词在本地生成",
"ChineseRomanization": "中文罗马音",
Expand Down
1 change: 1 addition & 0 deletions data/locale/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"SearchingProviders": "正在搜尋歌詞來源…",
"SelectCandidatePreview": "選擇候選歌詞後會在這裡預覽",
"SettingsWindowTitle": "FloatLyrics 設定",
"OverlayWindowTitle": "FloatLyrics 歌詞",
"ShowRomanization": "顯示羅馬拼音",
"ShowRomanizationDescription": "一律根據原歌詞在本機產生",
"ChineseRomanization": "中文羅馬拼音",
Expand Down
1 change: 1 addition & 0 deletions floatlyrics-core/src/i18n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ macro_rules! define_text_keys {

define_text_keys!(
SettingsWindowTitle,
OverlayWindowTitle,
ChangesSavedAutomatically,
Saved,
SaveFailed,
Expand Down
111 changes: 76 additions & 35 deletions src/frontend/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,17 @@ pub(super) fn build(
about: Rc<serde_json::Value>,
sender: relm4::Sender<AppMsg>,
) -> OverlayView {
let drag_mode = desktop_drag_mode(std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref());
let layer_shell_supported = gtk4_layer_shell::is_supported();
if !layer_shell_supported {
tracing::warn!(
"the compositor does not support the layer shell protocol; \
falling back to a plain draggable window"
);
}
let drag_mode = overlay_drag_mode(
layer_shell_supported,
std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref(),
);
let panel_width = compact_panel_width(config.window.width);
let initial_placement = if config.window.remember_position {
config.window.position.map(WindowPlacement::from_position)
Expand All @@ -113,38 +123,45 @@ pub(super) fn build(
config.lyrics.apple_music_style,
);
let fallback_height = fallback_panel_height(viewport_h);
window.set_title(Some("FloatLyrics Overlay"));
window.set_title(Some(i18n.text(Text::OverlayWindowTitle)));
window.set_decorated(false);
window.set_resizable(false);

window.init_layer_shell();
window.set_namespace(Some("floatlyrics"));
window.set_layer(Layer::Overlay);
window.set_keyboard_mode(KeyboardMode::None);
window.set_anchor(Edge::Bottom, true);
window.set_anchor(Edge::Left, true);
// KWin can apply desktop effects whenever a layer surface moves. On KDE,
// keep the surface fixed to the output and drag only its child panel.
window.set_anchor(Edge::Right, drag_mode.is_internal());
window.set_anchor(Edge::Top, drag_mode.is_internal());
if !drag_mode.is_internal() {
window.set_margin(
Edge::Left,
initial_placement
.and_then(|placement| left_position_for_width(window, &placement, panel_width))
.or_else(|| initial_x(panel_width))
.unwrap_or_default(),
);
window.set_margin(
Edge::Bottom,
initial_placement
.and_then(|placement| {
bottom_margin_from_placement(window, &placement, panel_width, fallback_height)
})
.unwrap_or_else(|| effective_bottom_margin(config)),
);
if drag_mode.uses_layer_shell() {
window.init_layer_shell();
window.set_namespace(Some("floatlyrics"));
window.set_layer(Layer::Overlay);
window.set_keyboard_mode(KeyboardMode::None);
window.set_anchor(Edge::Bottom, true);
window.set_anchor(Edge::Left, true);
// KWin can apply desktop effects whenever a layer surface moves. On KDE,
// keep the surface fixed to the output and drag only its child panel.
window.set_anchor(Edge::Right, drag_mode.is_internal());
window.set_anchor(Edge::Top, drag_mode.is_internal());
if !drag_mode.is_internal() {
window.set_margin(
Edge::Left,
initial_placement
.and_then(|placement| left_position_for_width(window, &placement, panel_width))
.or_else(|| initial_x(panel_width))
.unwrap_or_default(),
);
window.set_margin(
Edge::Bottom,
initial_placement
.and_then(|placement| {
bottom_margin_from_placement(
window,
&placement,
panel_width,
fallback_height,
)
})
.unwrap_or_else(|| effective_bottom_margin(config)),
);
}
window.set_exclusive_zone(-1);
}
window.set_exclusive_zone(-1);
window.add_css_class("floating-window");

super::style::install(
Expand Down Expand Up @@ -198,6 +215,8 @@ pub(super) fn build(
mode: drag_mode,
},
move |placement| placement_view.set_overlay_placement(snap_classes(&placement)),
// The plain-window fallback never calls this: a compositor-driven move
// reports no position to persist.
move |position| {
let _ = sender.send(AppMsg::WindowMoved(position));
if drag_mode.is_internal() {
Expand Down Expand Up @@ -235,7 +254,9 @@ pub(super) fn build(
panel_width,
fallback_height,
);
setup_input_region(window, &content);
if !placement.uses_native_window() {
setup_input_region(window, &content);
}
gtk::glib::idle_add_local_once(move || {
let picked = web_stack.pick(16.0, 16.0, gtk::PickFlags::DEFAULT);
if picked
Expand Down Expand Up @@ -269,6 +290,10 @@ pub(super) fn build(
{
let overlay = overlay.clone();
i18n.subscribe(move |language| {
// Only the plain-window fallback shows this title to the user.
overlay
.window
.set_title(Some(language.text(Text::OverlayWindowTitle)));
overlay.render_overlay_state(language);
let static_status = overlay.state.static_status();
if let Some(key) = static_status {
Expand All @@ -290,7 +315,9 @@ fn apply_panel_width(
content.set_width_request(width);
lyrics_viewport.set_width_request(width);
reposition_for_width(window, stage, content, placement, width);
setup_input_region(window, content);
if !placement.uses_native_window() {
setup_input_region(window, content);
}
}

fn set_status_lyrics(floating: &OverlayView, message: &str, key: Text) {
Expand Down Expand Up @@ -371,7 +398,7 @@ impl OverlayView {
width,
fallback_height,
);
} else {
} else if self.placement.uses_layer_margins() {
self.window.set_margin(
Edge::Bottom,
bottom_margin_from_placement(
Expand Down Expand Up @@ -413,12 +440,19 @@ impl OverlayView {
width,
fallback_height,
);
} else if let Some(bottom_margin) =
bottom_margin_from_placement(&window, &placement.current(), width, fallback_height)
} else if placement.uses_layer_margins()
&& let Some(bottom_margin) = bottom_margin_from_placement(
&window,
&placement.current(),
width,
fallback_height,
)
{
window.set_margin(Edge::Bottom, bottom_margin);
}
setup_input_region(&window, &content);
if !placement.uses_native_window() {
setup_input_region(&window, &content);
}
});
}

Expand Down Expand Up @@ -540,6 +574,13 @@ fn track_offset_label(offset_ms: i64, unit: &str) -> String {
}
}

fn overlay_drag_mode(layer_shell_supported: bool, desktop: Option<&str>) -> DragMode {
if !layer_shell_supported {
return DragMode::ToplevelWindow;
}
desktop_drag_mode(desktop)
}

fn desktop_drag_mode(desktop: Option<&str>) -> DragMode {
if desktop.is_some_and(|desktop| {
desktop
Expand Down
Loading
Loading