Skip to content

Commit

Permalink
Image previews
Browse files Browse the repository at this point in the history
Additionally to enable the preview feature, one must add it to
config.json:
```json
    "image_preview": {}
```
See documentation for additional settings - they shouldn't be necessary
in most cases.

Windows is basically unsupported, but it is possible to force the
feature, if the `protocol` option is set with `type` and `font_size`.
  • Loading branch information
benjajaja committed Oct 16, 2023
1 parent b2b47ed commit b3e97f8
Show file tree
Hide file tree
Showing 14 changed files with 528 additions and 25 deletions.
48 changes: 48 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,19 @@ features = ["e2e-encryption", "sled", "rustls-tls"]
version = "1.24.1"
features = ["macros", "net", "rt-multi-thread", "sync", "time"]

[dependencies.ratatui-image]
version = "0.3.2"
features = ["serde"]

[target.'cfg(not(target_os = "windows"))'.dependencies.ratatui-image]
version = "0.3.2"
features = ["serde", "sixel", "rustix"]

[dev-dependencies]
lazy_static = "1.4.0"
pretty_assertions = "1.4.0"

[profile.release]
lto = true
incremental = false

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ two other TUI clients and Element Web:
| Room upgrades | ❌ ([#41]) | ✔️ || ✔️ |
| Localisations || 1 || 44 |
| SSO Support || ✔️ | ✔️ | ✔️ |
| Image preview | ✔️ ||| ✔️ |
## License

Expand Down
11 changes: 10 additions & 1 deletion docs/example_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@
"color": "magenta"
}
},
"default_room": "#iamb-users:0x.badd.cafe"
"default_room": "#iamb-users:0x.badd.cafe",
"image_preview": {
"protocol": {
"type": "sixel"
},
"size": {
"width": 66,
"height": 10
}
}
},
"dirs": {
"cache": "/home/user/.cache/iamb/",
Expand Down
15 changes: 15 additions & 0 deletions docs/iamb.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,18 @@ overridden as described in *PROFILES*.
**default_room** (type: string)
> The room to show by default instead of a welcome-screen.
**image_preview** (type: image_preview object)
> Enable image previews and configure it. An empty object will enable the
> feature with default settings, omitting it will disable the feature.
> *size* is an optional object with *width* and *height* numbers, which are
> used te set the preview size in characters. Defaults to 66 and 10.
> *protocol* is an optional object to override settings that should normally
> be guessed automatically.
> *protocol.type* is an optional string with one of the protocol types:
> _sixel_, _kitty_, _halfblocks_.
> *protocol.font_size* is an optional list of two numbers representing font
> width and height in pixels.
## USER OVERRIDES

Overrides are mapped onto matrix User IDs such as _@user:matrix.org_ and are
Expand Down Expand Up @@ -127,6 +139,9 @@ Specifies the directories to save data in. Configured as a map under the key
**downloads** (type: string)
> Specifies where to store downloaded files.
**image_previews** (type: string)
> Specifies where to store automatically downloaded image previews.
# SEE ALSO

*iamb(1)*
Expand Down
14 changes: 10 additions & 4 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,24 @@
pname = "iamb";
version = "0.0.7";
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"modalkit-0.0.16" = "sha256-mjAD1v0r2+SzPdoB2wZ/5iJ1NZK+3OSvCYcUZ5Ef38Y=";
};
};
nativeBuildInputs = [ pkgs.pkgconfig ];
buildInputs = [ pkgs.openssl ] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin
(with pkgs.darwin.apple_sdk.frameworks; [ AppKit Security ]);
};
devShell = mkShell {
buildInputs = [
(rustNightly.override { extensions = [ "rust-src" ]; })
(rustNightly.override {
extensions = [ "rust-src" "rust-analyzer-preview" "rustfmt" "clippy" ];
})
pkg-config
cargo-tarpaulin
rust-analyzer
rustfmt
cargo-watch
];
};
});
Expand Down
45 changes: 44 additions & 1 deletion src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::sync::Arc;
use std::time::{Duration, Instant};

use emojis::Emoji;
use ratatui_image::picker::Picker;
use serde::{
de::Error as SerdeError,
de::Visitor,
Expand Down Expand Up @@ -81,6 +82,8 @@ use modalkit::{
},
};

use crate::message::ImageBackend;
use crate::preview::{source_from_event, spawn_insert_preview};
use crate::{
message::{Message, MessageEvent, MessageKey, MessageTimeStamp, Messages},
worker::Requester,
Expand Down Expand Up @@ -487,6 +490,12 @@ pub enum IambError {
/// A failure to access the system's clipboard.
#[error("Could not use system clipboard data")]
Clipboard,

#[error("IO error: {0}")]
IOError(#[from] std::io::Error),

#[error("Preview error: {0}")]
Preview(String),
}

impl From<IambError> for UIError<IambInfo> {
Expand Down Expand Up @@ -607,6 +616,10 @@ impl RoomInfo {
self.messages.get(self.get_message_key(event_id)?)
}

pub fn get_event_mut(&mut self, event_id: &EventId) -> Option<&mut Message> {
self.messages.get_mut(self.keys.get(event_id)?.to_message_key()?)
}

/// Insert a reaction to a message.
pub fn insert_reaction(&mut self, react: ReactionEvent) {
match react {
Expand Down Expand Up @@ -697,6 +710,35 @@ impl RoomInfo {
}
}

pub fn insert_with_preview(
&mut self,
room_id: OwnedRoomId,
store: AsyncProgramStore,
picker: Option<Picker>,
ev: matrix_sdk::ruma::events::MessageLikeEvent<RoomMessageEventContent>,
settings: &mut ApplicationSettings,
media: matrix_sdk::Media,
) {
let source = picker.and_then(|_| source_from_event(&ev));
self.insert(ev);

if let Some((event_id, source)) = source {
if let (Some(msg), Some(image_preview)) =
(self.get_event_mut(&event_id), &settings.tunables.image_preview)
{
msg.image_backend = ImageBackend::Downloading(image_preview.size.clone());
spawn_insert_preview(
store,
room_id,
event_id,
source,
media,
settings.dirs.image_previews.clone(),
)
}
}
}

/// Indicates whether we've recently fetched scrollback for this room.
pub fn recently_fetched(&self) -> bool {
self.fetch_last.map_or(false, |i| i.elapsed() < ROOM_FETCH_DEBOUNCE)
Expand Down Expand Up @@ -850,6 +892,7 @@ pub struct ChatStore {

/// Information gathered by the background thread.
pub sync_info: SyncInfo,
pub picker: Option<Picker>,
}

impl ChatStore {
Expand All @@ -858,7 +901,7 @@ impl ChatStore {
ChatStore {
worker,
settings,

picker: None,
cmds: crate::commands::setup_commands(),
emojis: emoji_map(),

Expand Down
41 changes: 40 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::process;

use clap::Parser;
use matrix_sdk::ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UserId};
use ratatui_image::picker::ProtocolType;
use serde::{de::Error as SerdeError, de::Visitor, Deserialize, Deserializer};
use tracing::Level;
use url::Url;
Expand Down Expand Up @@ -246,6 +247,31 @@ pub enum UserDisplayStyle {
DisplayName,
}

#[derive(Clone, Deserialize)]
pub struct ImagePreviewValues {
pub size: ImagePreviewSize,
pub protocol: Option<ImagePreviewProtocolValues>,
}
#[derive(Clone, Deserialize)]
pub struct ImagePreviewProtocolValues {
pub r#type: Option<ProtocolType>,
pub font_size: Option<(u16, u16)>,
}
impl Default for ImagePreviewValues {
fn default() -> Self {
ImagePreviewValues {
size: ImagePreviewSize { width: 66, height: 10 },
protocol: None,
}
}
}

#[derive(Clone, Deserialize)]
pub struct ImagePreviewSize {
pub width: usize,
pub height: usize,
}

#[derive(Clone)]
pub struct TunableValues {
pub log_level: Level,
Expand All @@ -260,6 +286,7 @@ pub struct TunableValues {
pub username_display: UserDisplayStyle,
pub default_room: Option<String>,
pub open_command: Option<Vec<String>>,
pub image_preview: Option<ImagePreviewValues>,
}

#[derive(Clone, Default, Deserialize)]
Expand All @@ -276,6 +303,7 @@ pub struct Tunables {
pub username_display: Option<UserDisplayStyle>,
pub default_room: Option<String>,
pub open_command: Option<Vec<String>>,
pub image_preview: Option<ImagePreviewValues>,
}

impl Tunables {
Expand All @@ -295,6 +323,7 @@ impl Tunables {
username_display: self.username_display.or(other.username_display),
default_room: self.default_room.or(other.default_room),
open_command: self.open_command.or(other.open_command),
image_preview: self.image_preview.or(other.image_preview),
}
}

Expand All @@ -312,6 +341,7 @@ impl Tunables {
username_display: self.username_display.unwrap_or_default(),
default_room: self.default_room,
open_command: self.open_command,
image_preview: self.image_preview,
}
}
}
Expand All @@ -321,13 +351,15 @@ pub struct DirectoryValues {
pub cache: PathBuf,
pub logs: PathBuf,
pub downloads: Option<PathBuf>,
pub image_previews: PathBuf,
}

#[derive(Clone, Default, Deserialize)]
pub struct Directories {
pub cache: Option<PathBuf>,
pub logs: Option<PathBuf>,
pub downloads: Option<PathBuf>,
pub image_previews: Option<PathBuf>,
}

impl Directories {
Expand All @@ -336,6 +368,7 @@ impl Directories {
cache: self.cache.or(other.cache),
logs: self.logs.or(other.logs),
downloads: self.downloads.or(other.downloads),
image_previews: self.image_previews.or(other.image_previews),
}
}

Expand All @@ -357,7 +390,13 @@ impl Directories {

let downloads = self.downloads.or_else(dirs::download_dir);

DirectoryValues { cache, logs, downloads }
let image_previews = self.image_previews.unwrap_or_else(|| {
let mut dir = cache.clone();
dir.push("image_preview_downloads");
dir
});

DirectoryValues { cache, logs, downloads, image_previews }
}
}

Expand Down
Loading

0 comments on commit b3e97f8

Please sign in to comment.