feat(recent): Convert from GJS to Rust. GJS no longer required

This commit is contained in:
Michael Aaron Murphy 2021-08-24 21:56:42 +02:00
parent 2aa86e4ff7
commit dbcf28b3d8
10 changed files with 519 additions and 153 deletions

View file

@ -4,6 +4,7 @@ pub mod files;
pub mod find;
pub mod pop_shell;
pub mod pulse;
pub mod recent;
pub mod scripts;
pub mod terminal;
pub mod web;

View file

@ -131,7 +131,7 @@ async fn command_spawn(cmd: &str, args: &[&str]) -> io::Result<()> {
.args(args)
.spawn()?;
AsyncPidFd::from_pid(child.id() as i32)?.wait().await;
let _ = AsyncPidFd::from_pid(child.id() as i32)?.wait().await;
Ok(())
}
@ -152,7 +152,7 @@ fn pactl_sinks() -> postage::mpsc::Receiver<String> {
while let Some(Ok(line)) = lines.next().await {
if let Some(stripped) = line.strip_prefix("Sink #") {
use postage::prelude::Sink;
tx.send(stripped.trim().to_owned()).await;
let _ = tx.send(stripped.trim().to_owned()).await;
}
}
}

View file

@ -1,21 +1,32 @@
use futures_lite::prelude::*;
use gtk::prelude::*;
use pop_launcher::*;
use slab::Slab;
use smol::Unblock;
use std::io;
use std::{borrow::Cow, io};
pub struct App {
manager: gtk::RecentManager,
out: Unblock<io::Stdout>,
uris: Slab<String>,
}
impl Default for App {
fn default() -> Self {
Self {
manager: gtk::RecentManager::new(),
out: async_stdout(),
uris: Slab::new(),
}
}
}
pub async fn main() {
if gtk::init().is_err() {
tracing::error!("failed to initialize GTK");
return;
}
let mut requests = json_input_stream(async_stdin());
let mut app = App::default();
@ -36,9 +47,47 @@ pub async fn main() {
}
impl App {
async fn activate(&mut self, id: u32) {}
async fn activate(&mut self, id: u32) {
if let Some(uri) = self.uris.get(id as usize) {
crate::xdg_open(uri);
crate::send(&mut self.out, PluginResponse::Close).await;
}
}
async fn search(&mut self, query: String) {
self.uris.clear();
if let Some(query) = normalized(&query) {
for item in self.manager.items() {
if let Some(name) = item.display_name() {
if name.to_ascii_lowercase().contains(&query) {
if let Some((mime, uri)) = item.mime_type().zip(item.uri()) {
let id = self.uris.insert(uri.to_string());
crate::send(
&mut self.out,
PluginResponse::Append(PluginSearchResult {
id: id as u32,
name: name.to_string(),
description: item
.uri_display()
.map(String::from)
.unwrap_or_default(),
icon: Some(IconSource::Mime(Cow::Owned(mime.to_string()))),
..Default::default()
}),
)
.await;
}
}
}
}
}
crate::send(&mut self.out, PluginResponse::Finished).await;
}
}
fn normalized(input: &str) -> Option<String> {
input
.find(' ')
.map(|pos| input[pos + 1..].trim().to_ascii_lowercase())
}

View file

@ -6,6 +6,6 @@
help: "recent ",
isolate: true
),
bin: (path: "recent.js"),
bin: (path: "recent"),
icon: Name("system-file-manager")
)

View file

@ -1,142 +0,0 @@
#!/usr/bin/gjs
const { GLib, Gio, Gtk } = imports.gi
const STDIN = new Gio.DataInputStream({ base_stream: new Gio.UnixInputStream({ fd: 0 }) })
const STDOUT = new Gio.DataOutputStream({ base_stream: new Gio.UnixOutputStream({ fd: 1 }) })
class App {
constructor() {
this.last_query = ""
this.manager = Gtk.RecentManager.get_default()
this.results = new Array()
}
/**
* @returns {null | Array<RecentItem>}
*/
items() {
const recent_items = this.manager.get_items()
log(`got items`)
if (!recent_items) { return null }
const items = new Array()
for (const item of recent_items) {
if (item.exists()) {
items.push({
display_name: item.get_display_name(),
mime: item.get_mime_type(),
uri: item.get_uri()
})
}
}
return items
}
query(input) {
input = input.substr(input.indexOf(" ") + 1).trim()
try {
const items = this.items()
if (items) {
const normalized = input.toLowerCase()
this.results = items
.filter(item => item.display_name.toLowerCase().includes(normalized))
.sort((a, b) => a.display_name.localeCompare(b.display_name))
.slice(0, 7)
log(`sorted`)
let id = 0
for (const item of this.results) {
this.send({ "Append": {
id,
name: item.display_name,
description: decodeURI(item.uri),
icon: { Mime: item.mime }
}})
id += 1
}
}
} catch (why) {
log(`query exception: ${why}`)
}
this.send("Finished")
}
submit(id) {
const result = this.results[id]
if (result) {
try {
GLib.spawn_command_line_async(`xdg-open '${result.uri}'`)
} catch (e) {
log(`xdg-open failed: ${e}`)
}
}
this.send("Close")
}
send(object) {
STDOUT.write_bytes(new GLib.Bytes(JSON.stringify(object) + "\n"), null)
STDOUT.flush(null)
}
}
function main() {
/** @type {null | ByteArray} */
let input_array
/** @type {string} */
let input_str
/** @type {null | LauncherRequest} */
let event
let app = new App()
mainloop:
while (true) {
try {
[input_array,] = STDIN.read_line(null)
} catch (e) {
break
}
input_str = imports.byteArray.toString(input_array)
if ((event = parse_event(input_str)) !== null) {
if ("Search" in event) {
app.query(event.Search)
} else if ("Activate" in event) {
app.submit(event.Activate);
} else if (event === "Exit") {
break mainloop
}
}
}
}
/**
* Parses an IPC event received from STDIN
* @param {string} input
* @returns {null | LauncherRequest}
*/
function parse_event(input) {
try {
return JSON.parse(input)
} catch (e) {
log(`Input not valid JSON`)
return null
}
}
main()