refactor: remove dead code (#3337)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-09 01:41:26 +02:00
1 parent a19002652b
commit 93d1f8af46
1527 files changed
+21170 -73846

No files matched your search

-35
View File
@@ -11,41 +11,6 @@ export declare function resolveKwinWindowPid(token: string): Promise<number | nu
export declare function resolveX11WindowPid(token: string): Promise<number | null>;
export type FileChooserFilterKind = 0 | 1;
export interface FileChooserFilterRule {
kind: FileChooserFilterKind;
pattern: string;
}
export interface FileChooserFilter {
name: string;
rules: ReadonlyArray<FileChooserFilterRule>;
}
export interface FileChooserOptions {
parentWindow?: string;
title?: string;
acceptLabel?: string;
modal?: boolean;
multiple?: boolean;
directory?: boolean;
currentFolder?: string;
currentName?: string;
currentFile?: string;
filters?: ReadonlyArray<FileChooserFilter>;
currentFilter?: FileChooserFilter;
}
export interface FileChooserResult {
cancelled: boolean;
uris: Array<string>;
}
export declare function openFile(options: FileChooserOptions): Promise<FileChooserResult>;
export declare function saveFile(options: FileChooserOptions): Promise<FileChooserResult>;
export interface BackgroundOptions {
reason?: string;
autostart?: boolean;
@@ -61,8 +61,6 @@ module.exports = {
resolveKwinWindowPid: binding ? binding.resolveKwinWindowPid : null,
resolveX11WindowPid: binding ? binding.resolveX11WindowPid : null,
resolveWindowPid: binding ? binding.resolveWindowPid : null,
openFile: binding ? binding.openFile : null,
saveFile: binding ? binding.saveFile : null,
requestBackground: binding ? binding.requestBackground : null,
GlobalShortcutsPortal: binding ? binding.GlobalShortcutsPortal : null,
SessionStateMonitor: binding ? binding.SessionStateMonitor : null,
@@ -1,334 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
#[cfg(target_os = "linux")]
use std::{collections::HashMap, sync::mpsc, time::Duration};
#[cfg(target_os = "linux")]
use futures_lite::{FutureExt, StreamExt, future};
#[cfg(target_os = "linux")]
use zbus::{
MatchRule, MessageStream,
blocking::{Connection as BlockingConnection, Proxy as BlockingProxy},
message::Type as MessageType,
zvariant::{OwnedObjectPath, Value},
};
#[cfg(target_os = "linux")]
use crate::portal::{REQUEST_INTERFACE, mint_token, request_path};
pub const PORTAL_DESTINATION: &str = "org.freedesktop.portal.Desktop";
pub const PORTAL_PATH: &str = "/org/freedesktop/portal/desktop";
pub const FILE_CHOOSER_INTERFACE: &str = "org.freedesktop.portal.FileChooser";
#[cfg(target_os = "linux")]
pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(5 * 60);
#[cfg(target_os = "linux")]
pub const SIGNAL_POLL_INTERVAL: Duration = Duration::from_millis(200);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Open,
Save,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FilterRule {
pub kind: u32,
pub pattern: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Filter {
pub name: String,
pub rules: Vec<FilterRule>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Options {
pub parent_window: String,
pub title: String,
pub accept_label: Option<String>,
pub modal: bool,
pub multiple: bool,
pub directory: bool,
pub current_folder: Option<String>,
pub current_name: Option<String>,
pub current_file: Option<String>,
pub filters: Vec<Filter>,
pub current_filter: Option<Filter>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FileChooserResult {
pub cancelled: bool,
pub uris: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FileChooserError {
DbusError,
PortalTimeout,
InvalidReply,
SendFailed,
}
impl std::fmt::Display for FileChooserError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::DbusError => "DbusError",
Self::PortalTimeout => "PortalTimeout",
Self::InvalidReply => "InvalidReply",
Self::SendFailed => "SendFailed",
};
f.write_str(s)
}
}
#[cfg(target_os = "linux")]
pub fn invoke(mode: Mode, options: Options) -> Result<FileChooserResult, FileChooserError> {
let conn = zbus::blocking::connection::Builder::session()
.map_err(|_| FileChooserError::DbusError)?
.method_timeout(Duration::from_secs(30))
.build()
.map_err(|_| FileChooserError::DbusError)?;
let unique_owned = conn
.unique_name()
.ok_or(FileChooserError::DbusError)?
.to_owned();
let unique_name = unique_owned.as_str().to_string();
let token_prefix = match mode {
Mode::Open => "fluxer_fc_open",
Mode::Save => "fluxer_fc_save",
};
let handle_token = mint_token(token_prefix);
let expected_path = request_path(&unique_name, &handle_token);
let (tx, rx) = mpsc::sync_channel::<FileChooserResponse>(1);
let stop_flag = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let stop_for_thread = stop_flag.clone();
let expected_for_thread = expected_path.clone();
let listener = std::thread::Builder::new()
.name("fluxer-linux-portals-fc".to_string())
.spawn(move || {
response_listener(&expected_for_thread, tx, stop_for_thread);
})
.map_err(|_| FileChooserError::DbusError)?;
let send_result = send_call(&conn, mode, &handle_token, &options, &expected_path);
let result = match send_result {
Ok(()) => match rx.recv_timeout(REQUEST_TIMEOUT) {
Ok(response) => {
if response.code != 0 {
Ok(FileChooserResult {
cancelled: true,
uris: vec![],
})
} else {
Ok(FileChooserResult {
cancelled: false,
uris: response.uris,
})
}
}
Err(_) => Err(FileChooserError::PortalTimeout),
},
Err(err) => Err(err),
};
stop_flag.store(true, std::sync::atomic::Ordering::Release);
let _ = listener.join();
result
}
#[cfg(target_os = "linux")]
struct FileChooserResponse {
code: u32,
uris: Vec<String>,
}
#[cfg(target_os = "linux")]
fn response_listener(
expected_path: &str,
tx: mpsc::SyncSender<FileChooserResponse>,
stop: std::sync::Arc<std::sync::atomic::AtomicBool>,
) {
let setup = future::block_on(async {
let conn = zbus::Connection::session().await?;
let rule = MatchRule::builder()
.msg_type(MessageType::Signal)
.interface(REQUEST_INTERFACE)?
.member("Response")?
.path(expected_path.to_string())?
.build();
let stream = MessageStream::for_match_rule(rule, &conn, Some(8)).await?;
zbus::Result::Ok((conn, stream))
});
let (_conn, mut stream) = match setup {
Ok(parts) => parts,
Err(_) => return,
};
while !stop.load(std::sync::atomic::Ordering::Acquire) {
let timeout = async {
async_io::Timer::after(SIGNAL_POLL_INTERVAL).await;
None::<zbus::Result<zbus::Message>>
};
match future::block_on(stream.next().or(timeout)) {
Some(Ok(message)) => {
if let Some(parsed) = parse_filechooser_response(&message) {
let _ = tx.send(parsed);
return;
}
}
Some(Err(_)) => return,
None => {}
}
}
}
#[cfg(target_os = "linux")]
fn parse_filechooser_response(message: &zbus::Message) -> Option<FileChooserResponse> {
let body = message.body();
let (code, results): (u32, HashMap<String, zbus::zvariant::OwnedValue>) =
body.deserialize().ok()?;
let mut uris: Vec<String> = Vec::new();
if let Some(v) = results.get("uris") {
let val = crate::kwin::value_of_owned(v);
if let Value::Array(arr) = val {
for element in arr.iter() {
let inner: &Value<'_> = match element {
Value::Value(b) => b.as_ref(),
other => other,
};
if let Value::Str(s) = inner {
uris.push(s.as_str().to_string());
}
}
}
}
Some(FileChooserResponse { code, uris })
}
#[cfg(target_os = "linux")]
fn send_call(
conn: &BlockingConnection,
mode: Mode,
handle_token: &str,
options: &Options,
expected_path: &str,
) -> Result<(), FileChooserError> {
let proxy = BlockingProxy::new(
conn,
PORTAL_DESTINATION,
PORTAL_PATH,
FILE_CHOOSER_INTERFACE,
)
.map_err(|_| FileChooserError::DbusError)?;
let member = match mode {
Mode::Open => "OpenFile",
Mode::Save => "SaveFile",
};
let mut vardict: HashMap<&str, Value<'_>> = HashMap::new();
vardict.insert("handle_token", Value::new(handle_token));
vardict.insert("modal", Value::new(options.modal));
vardict.insert("multiple", Value::new(options.multiple));
if matches!(mode, Mode::Open) && options.directory {
vardict.insert("directory", Value::new(true));
}
if let Some(label) = options.accept_label.as_deref() {
vardict.insert("accept_label", Value::new(label));
}
if !options.filters.is_empty() {
vardict.insert("filters", Value::new(serialize_filters(&options.filters)));
}
if let Some(cf) = options.current_filter.as_ref() {
vardict.insert("current_filter", Value::new(serialize_filter(cf)));
}
if let Some(folder) = options.current_folder.as_deref() {
vardict.insert("current_folder", Value::new(folder.as_bytes()));
}
if matches!(mode, Mode::Save)
&& let Some(name) = options.current_name.as_deref()
{
vardict.insert("current_name", Value::new(name));
}
if matches!(mode, Mode::Save)
&& let Some(file) = options.current_file.as_deref()
{
vardict.insert("current_file", Value::new(file.as_bytes()));
}
let reply_path: OwnedObjectPath = proxy
.call(
member,
&(
options.parent_window.as_str(),
options.title.as_str(),
vardict,
),
)
.map_err(|_| FileChooserError::SendFailed)?;
if !reply_path.as_str().is_empty() && reply_path.as_str() != expected_path {
return Err(FileChooserError::InvalidReply);
}
Ok(())
}
#[cfg(target_os = "linux")]
fn serialize_filters(filters: &[Filter]) -> Vec<(&str, Vec<(u32, &str)>)> {
filters
.iter()
.map(|f| {
let rules: Vec<(u32, &str)> = f
.rules
.iter()
.map(|r| (r.kind, r.pattern.as_str()))
.collect();
(f.name.as_str(), rules)
})
.collect()
}
#[cfg(target_os = "linux")]
fn serialize_filter(filter: &Filter) -> (&str, Vec<(u32, &str)>) {
let rules: Vec<(u32, &str)> = filter
.rules
.iter()
.map(|r| (r.kind, r.pattern.as_str()))
.collect();
(filter.name.as_str(), rules)
}
#[cfg(not(target_os = "linux"))]
pub fn invoke(_mode: Mode, _options: Options) -> Result<FileChooserResult, FileChooserError> {
Err(FileChooserError::DbusError)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn options_default_is_open_safe() {
let opts = Options::default();
assert!(!opts.directory);
assert!(!opts.multiple);
assert!(opts.parent_window.is_empty());
}
#[test]
fn filter_rule_kinds_match_typescript_union() {
let glob = FilterRule {
kind: 0,
pattern: "*.png".into(),
};
let mime = FilterRule {
kind: 1,
pattern: "image/png".into(),
};
assert_eq!(glob.kind, 0);
assert_eq!(mime.kind, 1);
}
}
@@ -2,7 +2,6 @@
pub mod background;
pub mod env;
pub mod filechooser;
pub mod global_shortcuts;
pub mod gnome_shell;
pub mod kwin;
@@ -29,7 +28,6 @@ mod napi_bindings {
use crate::{
background::{self, RequestOptions, RequestResult},
filechooser::{self, FileChooserResult, Filter, FilterRule, Mode, Options},
global_shortcuts::{
BindOutcome, DEFAULT_SESSION_TOKEN, Desktop, GlobalShortcutsClient, OpenResult,
PortalEvent, PortalOptions, Responder, ShortcutBinding, ShortcutDefinition,
@@ -55,18 +53,10 @@ mod napi_bindings {
object.get::<String>(key).ok().flatten()
}
fn read_string_field_or_empty(object: &Object, key: &str) -> String {
read_string_field(object, key).unwrap_or_default()
}
fn read_bool_field(object: &Object, key: &str) -> Option<bool> {
object.get::<bool>(key).ok().flatten()
}
fn read_object_field<'a>(object: &Object<'a>, key: &str) -> Option<Object<'a>> {
object.get::<Object>(key).ok().flatten()
}
fn read_array_field<'a>(object: &Object<'a>, key: &str) -> Option<Array<'a>> {
object.get::<Array>(key).ok().flatten()
}
@@ -146,122 +136,6 @@ mod napi_bindings {
Ok(AsyncTask::new(ResolveWindowPidTask { token }))
}
fn parse_filter_rule(object: &Object) -> Result<FilterRule> {
let kind = object
.get::<u32>("kind")
.map_err(|err| invalid_arg(err.reason.clone()))?
.ok_or_else(|| invalid_arg("rule.kind must be a number"))?;
if kind > 1 {
return Err(invalid_arg("rule.kind must be 0 (glob) or 1 (mime-type)"));
}
let pattern = read_string_field(object, "pattern")
.ok_or_else(|| invalid_arg("rule.pattern must be a string"))?;
Ok(FilterRule { kind, pattern })
}
fn parse_filter(object: &Object) -> Result<Filter> {
let name = read_string_field(object, "name")
.ok_or_else(|| invalid_arg("filter.name must be a string"))?;
let rules_array = read_array_field(object, "rules")
.ok_or_else(|| invalid_arg("filter.rules must be an array"))?;
let mut rules = Vec::with_capacity(rules_array.len() as usize);
for i in 0..rules_array.len() {
let rule_obj = rules_array
.get::<Object>(i)
.map_err(|err| invalid_arg(err.reason.clone()))?
.ok_or_else(|| invalid_arg("rule must be an object"))?;
rules.push(parse_filter_rule(&rule_obj)?);
}
Ok(Filter { name, rules })
}
fn parse_filechooser_options(object: &Object) -> Result<Options> {
let parent_window = read_string_field_or_empty(object, "parentWindow");
let title = read_string_field_or_empty(object, "title");
let accept_label = read_string_field(object, "acceptLabel");
let modal = read_bool_field(object, "modal").unwrap_or(true);
let multiple = read_bool_field(object, "multiple").unwrap_or(false);
let directory = read_bool_field(object, "directory").unwrap_or(false);
let current_folder = read_string_field(object, "currentFolder");
let current_name = read_string_field(object, "currentName");
let current_file = read_string_field(object, "currentFile");
let filters = if let Some(array) = read_array_field(object, "filters") {
let mut out = Vec::with_capacity(array.len() as usize);
for i in 0..array.len() {
let f_obj = array
.get::<Object>(i)
.map_err(|err| invalid_arg(err.reason.clone()))?
.ok_or_else(|| invalid_arg("filter must be an object"))?;
out.push(parse_filter(&f_obj)?);
}
out
} else {
Vec::new()
};
let current_filter = if let Some(obj) = read_object_field(object, "currentFilter") {
Some(parse_filter(&obj)?)
} else {
None
};
Ok(Options {
parent_window,
title,
accept_label,
modal,
multiple,
directory,
current_folder,
current_name,
current_file,
filters,
current_filter,
})
}
pub struct FileChooserTask {
mode: Mode,
options: Options,
}
impl Task for FileChooserTask {
type Output = FileChooserResult;
type JsValue = Object<'static>;
fn compute(&mut self) -> Result<Self::Output> {
filechooser::invoke(self.mode, self.options.clone())
.map_err(|err| generic_error(format!("FileChooser portal: {err}")))
}
fn resolve(&mut self, env: Env, output: Self::Output) -> Result<Self::JsValue> {
let mut obj = Object::new(&env)?;
obj.set("cancelled", output.cancelled)?;
let mut array = env.create_array(output.uris.len() as u32)?;
for (i, uri) in output.uris.iter().enumerate() {
array.set(i as u32, uri.as_str())?;
}
obj.set("uris", array)?;
Ok(unsafe { std::mem::transmute::<Object<'_>, Object<'static>>(obj) })
}
}
#[napi(js_name = "openFile")]
pub fn open_file(options: Object) -> Result<AsyncTask<FileChooserTask>> {
let parsed = parse_filechooser_options(&options)?;
Ok(AsyncTask::new(FileChooserTask {
mode: Mode::Open,
options: parsed,
}))
}
#[napi(js_name = "saveFile")]
pub fn save_file(options: Object) -> Result<AsyncTask<FileChooserTask>> {
let parsed = parse_filechooser_options(&options)?;
Ok(AsyncTask::new(FileChooserTask {
mode: Mode::Save,
options: parsed,
}))
}
fn parse_string_array_field(object: &Object, key: &str) -> Result<Vec<String>> {
let Some(array) = read_array_field(object, key) else {
return Ok(Vec::new());