refactor(v2): collapse owlry-core into owlry single crate

Workspace shrinks from 2 members to 1. The daemon, IPC layer,
providers, config, frecency store, GTK4 UI, and CLI now live in a
single `crates/owlry` crate exposing both a library (so integration
tests can reach daemon types) and a binary.

Structural changes:
- crates/owlry-core/ deleted; all source moved into crates/owlry/src/
  via git mv to preserve history
- crates/owlry/src/lib.rs added with module declarations
- crates/owlry/src/main.rs rewritten as thin entry that uses owlry::*
- crates/owlry/src/providers/mod.rs absorbs owlry-core's providers/mod.rs
  and pulls dmenu into the same module tree
- All owlry_core:: refs in src/ rewritten to crate::
- All owlry_core:: refs in tests/ rewritten to owlry::
- systemd/owlryd.service: ExecStart=/usr/bin/owlry -d (single binary)
- justfile: drop owlry-core/owlry-lua/owlry-rune build steps; daemon
  runs via 'cargo run -p owlry -- -d'
- owlry version: 1.0.10 -> 2.0.0-dev

Tests: 178 still pass (156 lib + 14 ipc + 8 server). No test changes
needed — moved files retained their inline test modules.

Task #2 complete.
This commit is contained in:
2026-05-13 02:05:26 +02:00
parent 1d20754b66
commit 0a4a09037e
41 changed files with 1223 additions and 1305 deletions
Generated
+7 -19
View File
@@ -1823,33 +1823,21 @@ dependencies = [
[[package]]
name = "owlry"
version = "1.0.10"
dependencies = [
"clap",
"env_logger",
"futures-channel",
"glib-build-tools",
"gtk4",
"gtk4-layer-shell",
"libc",
"log",
"owlry-core",
"serde",
"serde_json",
"toml 0.8.23",
]
[[package]]
name = "owlry-core"
version = "1.3.6"
version = "2.0.0-dev"
dependencies = [
"chrono",
"clap",
"dirs",
"env_logger",
"expr-solver-lib",
"freedesktop-desktop-entry",
"fs2",
"futures-channel",
"fuzzy-matcher",
"glib-build-tools",
"gtk4",
"gtk4-layer-shell",
"libc",
"log",
"notify-rust",
"reqwest",
-1
View File
@@ -2,7 +2,6 @@
resolver = "2"
members = [
"crates/owlry",
"crates/owlry-core",
]
# Shared workspace settings
-51
View File
@@ -1,51 +0,0 @@
[package]
name = "owlry-core"
version = "1.3.6"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Core daemon for the Owlry application launcher"
[lib]
name = "owlry_core"
path = "src/lib.rs"
[[bin]]
name = "owlryd"
path = "src/main.rs"
[dependencies]
# Provider system
fuzzy-matcher = "0.3"
freedesktop-desktop-entry = "0.8"
# Data & config
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
fs2 = "0.4"
chrono = { version = "0.4", features = ["serde"] }
dirs = "5"
# Error handling
thiserror = "2"
# Signal handling
signal-hook = "0.3"
# Logging & notifications
log = "0.4"
env_logger = "0.11"
notify-rust = "4"
# Built-in providers
expr-solver-lib = "1"
reqwest = { version = "0.13", default-features = false, features = ["native-tls", "json", "blocking"] }
[dev-dependencies]
tempfile = "3"
[features]
default = []
dev-logging = []
-8
View File
@@ -1,8 +0,0 @@
pub mod config;
pub mod data;
pub mod filter;
pub mod ipc;
pub mod notify;
pub mod paths;
pub mod providers;
pub mod server;
-33
View File
@@ -1,33 +0,0 @@
use log::info;
use owlry_core::paths;
use owlry_core::server::Server;
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let sock = paths::socket_path();
info!("Starting owlryd daemon...");
// Ensure the socket parent directory exists
if let Err(e) = paths::ensure_parent_dir(&sock) {
eprintln!("Failed to create socket directory: {e}");
std::process::exit(1);
}
let server = match Server::bind(&sock) {
Ok(s) => s,
Err(e) => {
eprintln!("Failed to start owlryd: {e}");
std::process::exit(1);
}
};
// SIGTERM/SIGINT are handled inside Server::run() via signal-hook,
// which saves frecency before exiting.
if let Err(e) = server.run() {
eprintln!("Server error: {e}");
std::process::exit(1);
}
}
File diff suppressed because it is too large Load Diff
+38 -12
View File
@@ -1,19 +1,24 @@
[package]
name = "owlry"
version = "1.0.10"
version = "2.0.0-dev"
edition = "2024"
rust-version = "1.90"
description = "A lightweight, owl-themed application launcher for Wayland"
authors = ["Your Name <you@example.com>"]
authors = ["Owlibou"]
license = "GPL-3.0-or-later"
repository = "https://somegit.dev/Owlibou/owlry"
keywords = ["launcher", "wayland", "gtk4", "linux"]
categories = ["gui"]
[dependencies]
# Core backend library
owlry-core = { path = "../owlry-core" }
[lib]
name = "owlry"
path = "src/lib.rs"
[[bin]]
name = "owlry"
path = "src/main.rs"
[dependencies]
# GTK4 for the UI
gtk4 = { version = "0.10", features = ["v4_12"] }
@@ -23,19 +28,37 @@ gtk4-layer-shell = "0.7"
# Low-level syscalls for stdin detection (dmenu mode)
libc = "0.2"
# Logging
# Logging & notifications
log = "0.4"
env_logger = "0.11"
# Configuration (needed for config types used in app.rs/theme.rs)
serde = { version = "1", features = ["derive"] }
toml = "0.8"
notify-rust = "4"
# CLI argument parsing
clap = { version = "4", features = ["derive"] }
# IPC (Request/Response serialization)
# Configuration & serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.8"
# Provider system & search
fuzzy-matcher = "0.3"
freedesktop-desktop-entry = "0.8"
# Data & filesystem
fs2 = "0.4"
chrono = { version = "0.4", features = ["serde"] }
dirs = "5"
# Error handling
thiserror = "2"
# Signal handling (daemon)
signal-hook = "0.3"
# Built-in providers
expr-solver-lib = "1"
reqwest = { version = "0.13", default-features = false, features = ["native-tls", "json", "blocking"] }
# Async oneshot channel (background thread -> main loop)
futures-channel = "0.3"
@@ -44,7 +67,10 @@ futures-channel = "0.3"
# GResource compilation for bundled icons
glib-build-tools = "0.20"
[dev-dependencies]
tempfile = "3"
[features]
default = []
# Enable verbose debug logging (for development/testing builds)
dev-logging = ["owlry-core/dev-logging"]
dev-logging = []
+6 -6
View File
@@ -8,11 +8,11 @@ use gtk4::prelude::*;
use gtk4::{Application, CssProvider, gio};
use gtk4_layer_shell::{Edge, Layer, LayerShell};
use log::{debug, info, warn};
use owlry_core::config::Config;
use owlry_core::data::FrecencyStore;
use owlry_core::filter::ProviderFilter;
use owlry_core::paths;
use owlry_core::providers::{Provider, ProviderManager, ProviderType};
use crate::config::Config;
use crate::data::FrecencyStore;
use crate::filter::ProviderFilter;
use crate::paths;
use crate::providers::{Provider, ProviderManager, ProviderType};
use std::cell::RefCell;
use std::rc::Rc;
@@ -149,7 +149,7 @@ impl OwlryApp {
/// All other providers belong to the daemon (Phase 1 keeps the daemon as the
/// primary path).
fn create_local_backend(_config: &Config) -> SearchBackend {
use owlry_core::providers::{ApplicationProvider, CommandProvider};
use crate::providers::{ApplicationProvider, CommandProvider};
let core_providers: Vec<Box<dyn Provider>> = vec![
Box::new(ApplicationProvider::new()),
+5 -5
View File
@@ -5,11 +5,11 @@
use crate::client::CoreClient;
use log::warn;
use owlry_core::config::Config;
use owlry_core::data::FrecencyStore;
use owlry_core::filter::ProviderFilter;
use owlry_core::ipc::{ProviderDesc, ResultItem};
use owlry_core::providers::{ItemSource, LaunchItem, ProviderManager, ProviderType};
use crate::config::Config;
use crate::data::FrecencyStore;
use crate::filter::ProviderFilter;
use crate::ipc::{ProviderDesc, ResultItem};
use crate::providers::{ItemSource, LaunchItem, ProviderManager, ProviderType};
use std::sync::{Arc, Mutex};
/// Parameters needed to run a search query on a background thread.
+1 -1
View File
@@ -2,7 +2,7 @@
use clap::Parser;
use owlry_core::providers::ProviderType;
use crate::providers::ProviderType;
#[derive(Parser, Debug, Clone)]
#[command(
+3 -3
View File
@@ -3,7 +3,7 @@ use std::os::unix::net::UnixStream;
use std::path::{Path, PathBuf};
use std::time::Duration;
use owlry_core::ipc::{ProviderDesc, Request, Response, ResultItem};
use crate::ipc::{ProviderDesc, Request, Response, ResultItem};
/// Maximum allowed size for a single IPC response line (4 MiB).
/// Larger than the request limit because responses carry result sets.
@@ -111,10 +111,10 @@ impl CoreClient {
/// Default socket path: `$XDG_RUNTIME_DIR/owlry/owlry.sock`.
///
/// Delegates to `owlry_core::paths::socket_path()` to keep a single
/// Delegates to `crate::paths::socket_path()` to keep a single
/// source of truth.
pub fn socket_path() -> PathBuf {
owlry_core::paths::socket_path()
crate::paths::socket_path()
}
/// Send a search query and return matching results.
+20
View File
@@ -0,0 +1,20 @@
//! Owlry — Wayland application launcher.
//!
//! Single-crate layout (v2): the daemon, IPC layer, providers, and GTK4 UI
//! all live here. The binary entry point in `main.rs` selects between UI
//! launch (default) and daemon mode (`-d` / `--daemon`).
pub mod app;
pub mod backend;
pub mod cli;
pub mod client;
pub mod config;
pub mod data;
pub mod filter;
pub mod ipc;
pub mod notify;
pub mod paths;
pub mod providers;
pub mod server;
pub mod theme;
pub mod ui;
+15 -19
View File
@@ -1,16 +1,10 @@
mod app;
mod backend;
mod cli;
pub mod client;
mod providers;
mod theme;
mod ui;
use app::OwlryApp;
use cli::CliArgs;
use log::{info, warn};
use std::os::unix::io::AsRawFd;
use owlry::app::OwlryApp;
use owlry::cli::CliArgs;
use owlry::{client, paths, server};
#[cfg(feature = "dev-logging")]
use log::debug;
@@ -22,12 +16,11 @@ use log::debug;
fn try_acquire_lock() -> Option<std::fs::File> {
use std::os::unix::fs::OpenOptionsExt;
let lock_path = owlry_core::paths::socket_path()
let lock_path = paths::socket_path()
.parent()
.unwrap()
.join("owlry-ui.lock");
// Ensure the parent directory exists
if let Some(parent) = lock_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
@@ -51,17 +44,21 @@ fn main() {
// -d / --daemon: run the daemon in-process and exit when it stops.
if args.daemon {
let default_level = if cfg!(feature = "dev-logging") { "debug" } else { "info" };
let default_level = if cfg!(feature = "dev-logging") {
"debug"
} else {
"info"
};
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default_level))
.format_timestamp_millis()
.init();
let sock = owlry_core::paths::socket_path();
if let Err(e) = owlry_core::paths::ensure_parent_dir(&sock) {
let sock = paths::socket_path();
if let Err(e) = paths::ensure_parent_dir(&sock) {
eprintln!("Failed to create socket directory: {e}");
std::process::exit(1);
}
match owlry_core::server::Server::bind(&sock) {
match server::Server::bind(&sock) {
Ok(server) => {
if let Err(e) = server.run() {
eprintln!("Server error: {e}");
@@ -76,7 +73,7 @@ fn main() {
}
}
// No subcommand - launch the app
// Default: launch the UI.
let default_level = if cfg!(feature = "dev-logging") {
"debug"
} else {
@@ -100,7 +97,6 @@ fn main() {
let _lock_guard = match try_acquire_lock() {
Some(file) => file,
None => {
// Another instance holds the lock — send toggle to daemon and exit
info!("Another owlry instance detected, sending toggle");
let socket_path = client::CoreClient::socket_path();
if let Ok(mut client) = client::CoreClient::connect(&socket_path) {
@@ -118,7 +114,7 @@ fn main() {
info!("Starting Owlry launcher");
// Diagnostic: log critical environment variables
// Diagnostic: log critical environment variables.
let home = std::env::var("HOME").unwrap_or_else(|_| "<not set>".to_string());
let path = std::env::var("PATH").unwrap_or_else(|_| "<not set>".to_string());
let xdg_data = std::env::var("XDG_DATA_HOME").unwrap_or_else(|_| "<not set>".to_string());
+1 -1
View File
@@ -1,5 +1,5 @@
use log::debug;
use owlry_core::providers::{ItemSource, LaunchItem, Provider, ProviderType};
use crate::providers::{ItemSource, LaunchItem, Provider, ProviderType};
use std::io::{self, BufRead};
/// Provider for dmenu-style input from stdin
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
use owlry_core::config::AppearanceConfig;
use crate::config::AppearanceConfig;
/// Generate CSS with :root variables from config settings
pub fn generate_variables_css(config: &AppearanceConfig) -> String {
+10 -10
View File
@@ -9,10 +9,10 @@ use gtk4::{
ListBoxRow, Orientation, ScrolledWindow, SelectionMode, ToggleButton,
};
use log::info;
use owlry_core::config::Config;
use owlry_core::filter::ProviderFilter;
use owlry_core::ipc::ProviderDesc;
use owlry_core::providers::{ItemSource, LaunchItem, ProviderType};
use crate::config::Config;
use crate::filter::ProviderFilter;
use crate::ipc::ProviderDesc;
use crate::providers::{ItemSource, LaunchItem, ProviderType};
#[cfg(feature = "dev-logging")]
use log::debug;
@@ -382,7 +382,7 @@ impl MainWindow {
/// Build hints string for the status bar based on enabled built-in providers.
/// Plugin trigger hints (? web, / files, etc.) are not included here since
/// plugin availability is not tracked in ProvidersConfig.
fn build_hints(config: &owlry_core::config::ProvidersConfig) -> String {
fn build_hints(config: &crate::config::ProvidersConfig) -> String {
let mut parts: Vec<String> = vec![
"Tab: cycle".to_string(),
"↑↓: nav".to_string(),
@@ -1366,7 +1366,7 @@ impl MainWindow {
item.name, cmd
);
log::warn!("{}", msg);
owlry_core::notify::notify("Command blocked", &msg);
crate::notify::notify("Command blocked", &msg);
return;
}
}
@@ -1375,7 +1375,7 @@ impl MainWindow {
if item.command.is_empty() && !matches!(item.provider, ProviderType::Application) {
let msg = format!("Item '{}' has no command; cannot launch", item.name);
log::warn!("{}", msg);
owlry_core::notify::notify("Launch failed", &msg);
crate::notify::notify("Launch failed", &msg);
return;
}
@@ -1397,7 +1397,7 @@ impl MainWindow {
if let Err(e) = result {
let msg = format!("Failed to launch '{}': {}", item.name, e);
log::error!("{}", msg);
owlry_core::notify::notify("Launch failed", &msg);
crate::notify::notify("Launch failed", &msg);
}
}
@@ -1418,7 +1418,7 @@ impl MainWindow {
if !Path::new(desktop_path).exists() {
let msg = format!("Desktop file not found: {}", desktop_path);
log::error!("{}", msg);
owlry_core::notify::notify("Launch failed", &msg);
crate::notify::notify("Launch failed", &msg);
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, msg));
}
@@ -1435,7 +1435,7 @@ impl MainWindow {
if !uwsm_available {
let msg = "uwsm is enabled in config but not installed";
log::error!("{}", msg);
owlry_core::notify::notify("Launch failed", msg);
crate::notify::notify("Launch failed", msg);
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, msg));
}
+2 -2
View File
@@ -1,5 +1,5 @@
use owlry_core::ipc::ProviderDesc;
use owlry_core::providers::ProviderType;
use crate::ipc::ProviderDesc;
use crate::providers::ProviderType;
/// Display metadata for a provider.
pub struct ProviderMeta {
+5 -5
View File
@@ -1,6 +1,6 @@
use gtk4::prelude::*;
use gtk4::{Box as GtkBox, Image, Label, ListBoxRow, Orientation, Widget};
use owlry_core::providers::{LaunchItem, ProviderType};
use crate::providers::{LaunchItem, ProviderType};
#[allow(dead_code)]
pub struct ResultRow {
@@ -107,13 +107,13 @@ impl ResultRow {
} else {
// Default icon based on provider type (only core types, plugins should provide icons)
let default_icon = match &item.provider {
owlry_core::providers::ProviderType::Application => {
crate::providers::ProviderType::Application => {
"application-x-executable-symbolic"
}
owlry_core::providers::ProviderType::Command => "utilities-terminal-symbolic",
owlry_core::providers::ProviderType::Dmenu => "view-list-symbolic",
crate::providers::ProviderType::Command => "utilities-terminal-symbolic",
crate::providers::ProviderType::Dmenu => "view-list-symbolic",
// Plugins should provide their own icon; fallback to generic addon icon
owlry_core::providers::ProviderType::Plugin(_) => "application-x-addon-symbolic",
crate::providers::ProviderType::Plugin(_) => "application-x-addon-symbolic",
};
let img = Image::from_icon_name(default_icon);
img.set_pixel_size(32);
+2 -2
View File
@@ -46,7 +46,7 @@
//! }
//! ```
use owlry_core::providers::LaunchItem;
use crate::providers::LaunchItem;
/// Parse a submenu command and extract plugin_id and data
/// Returns (plugin_id, data) if command matches SUBMENU: format
@@ -66,7 +66,7 @@ pub fn is_submenu_item(item: &LaunchItem) -> bool {
#[cfg(test)]
mod tests {
use super::*;
use owlry_core::providers::{ItemSource, ProviderType};
use crate::providers::{ItemSource, ProviderType};
#[test]
fn test_parse_submenu_command() {
@@ -1,4 +1,4 @@
use owlry_core::ipc::{ProviderDesc, Request, Response, ResultItem};
use owlry::ipc::{ProviderDesc, Request, Response, ResultItem};
#[test]
fn test_query_request_roundtrip() {
@@ -2,8 +2,8 @@ use std::io::{BufRead, BufReader, Write};
use std::os::unix::net::UnixStream;
use std::thread;
use owlry_core::ipc::{Request, Response};
use owlry_core::server::Server;
use owlry::ipc::{Request, Response};
use owlry::server::Server;
/// Helper: send a JSON request line and read the JSON response line.
fn roundtrip(stream: &mut UnixStream, request: &Request) -> Response {
+2 -1
View File
@@ -451,7 +451,8 @@ This section captures in-progress state. Update freely as work proceeds.
- `163e68a` — plan doc
- `2fc976b` — D15D21 resolutions
- `ae4a903` — C-ABI demolition: tasks #3/#4/#5 done in one commit
- (next) — TDD characterization pass for demolition (Provider trait defaults, ProviderManager submenu/action dispatch, FromStr aliases, ItemSource simplification, CLI `-d` flag, plugin_list IPC rejection)
- `1d20754` — TDD characterization pass (+36 tests)
- (next) — Workspace collapse: owlry-core merged into owlry (task #2)
- **Tasks done:** #1 inventory, #3 delete C-ABI, #4 delete Rune+Lua crates, #5 delete config_editor (scripts never lived in this repo)
- **Tasks remaining (Phase 1):** #2 workspace collapse, #6 convert 8 plugins, #7 cargo features, #8 sys→power rename, #9 CLI subcommands, #10 auto-mode test, #11 final build+smoke
- **Stray processes from inventory phase:**
+5 -25
View File
@@ -8,25 +8,16 @@ default:
build:
cargo build --workspace
build-ui:
cargo build -p owlry
build-daemon:
cargo build -p owlry-core
release:
cargo build --workspace --release
release-daemon:
cargo build -p owlry-core --release
# === Run ===
run *ARGS:
cargo run -p owlry -- {{ARGS}}
run-daemon *ARGS:
cargo run -p owlry-core -- {{ARGS}}
cargo run -p owlry -- -d {{ARGS}}
# === Quality ===
@@ -50,25 +41,14 @@ install-local:
set -euo pipefail
echo "Building release..."
cargo build -p owlry --release --no-default-features
cargo build -p owlry-core --release
cargo build -p owlry-lua -p owlry-rune --release
cargo build -p owlry --release
echo "Creating directories..."
sudo mkdir -p /usr/lib/owlry/plugins
sudo mkdir -p /usr/lib/owlry/runtimes
echo "Installing binaries..."
echo "Installing binary..."
sudo install -Dm755 target/release/owlry /usr/bin/owlry
sudo install -Dm755 target/release/owlryd /usr/bin/owlryd
echo "Installing runtimes..."
[ -f target/release/libowlry_lua.so ] && sudo install -Dm755 target/release/libowlry_lua.so /usr/lib/owlry/runtimes/liblua.so
[ -f target/release/libowlry_rune.so ] && sudo install -Dm755 target/release/libowlry_rune.so /usr/lib/owlry/runtimes/librune.so
echo "Installing systemd service files..."
[ -f systemd/owlryd.service ] && sudo install -Dm644 systemd/owlryd.service /usr/lib/systemd/user/owlryd.service
[ -f systemd/owlryd.socket ] && sudo install -Dm644 systemd/owlryd.socket /usr/lib/systemd/user/owlryd.socket
sudo install -Dm644 systemd/owlryd.service /usr/lib/systemd/user/owlryd.service
sudo install -Dm644 systemd/owlryd.socket /usr/lib/systemd/user/owlryd.socket
echo "Done. Start daemon: systemctl --user enable --now owlryd.service"
+1 -1
View File
@@ -5,7 +5,7 @@ After=graphical-session.target
[Service]
Type=simple
ExecStart=/usr/bin/owlryd
ExecStart=/usr/bin/owlry -d
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=3