Skip to content
Draft
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
13 changes: 13 additions & 0 deletions .changes/fix-dynamic-linking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

---
"libappindicator": minor
"libappindicator-sys": minor
---

Load exclusively using dynamic linking

This change lets `dlopen` (through `ld.so`) handle what paths to search in for the respective libraries.
Additionally this fixes a mistake with the library filenames. Now using the `SONAME` instead of a symlinked name that happened to work when dev packages are installed.

**Breaking:** Support for `$APPDIR` based appImage detection is removed.
Though it _should_ still work, because appimages provide an `LD_LIBRARY_PATH` that would be equivalent to what our previous detection method was doing in rust.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
target
Cargo.lock
.tests
49 changes: 49 additions & 0 deletions .tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
FROM ubuntu:18.04 as build

# Dependencies
RUN apt-get update && \
apt-get install --no-install-recommends -y \
build-essential ca-certificates curl libappindicator3-dev

# Drop privileges or Rust will have trouble installing
RUN useradd -m --uid 1000 --user-group rust
USER rust
WORKDIR /home/rust/src

# Install toolchain
ENV RUST_TOOLCHAIN=nightly
ENV RUST_BACKTRACE=1
ENV CARGO_INCREMENTAL=0
ENV CARGO_PROFILE_DEV_DEBUG=0

RUN curl https://sh.rustup.rs -sSf | \
sh -s -- --default-toolchain $RUST_TOOLCHAIN -y
ENV PATH=/home/rust/.cargo/bin:$PATH
RUN rustup component add rust-src --toolchain $RUST_TOOLCHAIN

# Build our app.
COPY --chown=rust:rust . /home/rust/src
ARG build_cmd="cargo build --release --example=hello"
RUN sh -c "${build_cmd}"


FROM ubuntu:18.04

RUN apt-get update && \
apt-get install -y libcanberra-gtk3-module binutils

RUN mkdir -p /app

ARG with_lib=""
RUN apt-get install -y ${with_lib}

ENV TRAY_ICON_DIR /app/icons
COPY examples/rust-logo.png /app/icons/rust-logo.png
COPY --from=build /home/rust/src/target/release/examples/hello /app/bin/hello

# RUN ls -la /usr/lib/x86_64-linux-gnu/ | grep indicator;\
# ldconfig --print-cache | grep indic;\
# objdump -p /usr/lib/x86_64-linux-gnu/*indicator* | grep SONAME;\
# true

CMD ["/app/bin/hello"]
39 changes: 39 additions & 0 deletions .tests/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
version: "3.8"
services:

no-library:
environment: &env
RUST_BACKTRACE: 1
DISPLAY: ${DISPLAY}
volumes: &vol
- /tmp/.X11-unix:/tmp/.X11-unix
build: &base-build
context: ..
dockerfile: .tests/Dockerfile
args:
build_cmd: cargo build --release --example=hello
with_lib:

libayatana:
environment: *env
volumes: *vol
build:
<<: *base-build
args:
with_lib: libayatana-appindicator3-1

libappindicator:
environment: *env
volumes: *vol
build:
<<: *base-build
args:
with_lib: libappindicator3-1

both-libs:
environment: *env
volumes: *vol
build:
<<: *base-build
args:
with_lib: libayatana-appindicator3-1 libappindicator3-1
14 changes: 14 additions & 0 deletions .tests/hello-examples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash
set -e

readonly workdir=$(dirname $(dirname $(realpath $0)))
cd ${workdir}

echo "DISPLAY=${DISPLAY:-""}"
if [ ! -d /tmp/.X11-unix ]; then
echo "Directory /tmp/.X11-unix does not exist."
fi

xhost + # Note this disable access control on Xorg, letting containers connect.
docker-compose -f .tests/docker-compose.yml up --build || true
xhost - # This reenables access control
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ glib = "0.15"
gtk = "0.15"
gtk-sys = "0.15"
libappindicator-sys = { version = "0.7", path = "sys/" }

[features]
default = [ "backcompat" ]
backcompat = [ "libappindicator-sys/backcompat" ]
20 changes: 16 additions & 4 deletions examples/hello.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::env;
use std::path::Path;
use std::{
env,
path::{Path, PathBuf},
};

use gtk::prelude::*;
use libappindicator::{AppIndicator, AppIndicatorStatus};
Expand All @@ -8,9 +10,19 @@ fn main() {
gtk::init().unwrap();
let mut indicator = AppIndicator::new("libappindicator test application", "");
indicator.set_status(AppIndicatorStatus::Active);
let icon_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples");

let icon_path = match env::var("TRAY_ICON_DIR") {
Ok(dir) => PathBuf::from(dir),
_ => Path::new(env!("CARGO_MANIFEST_DIR")).join("examples"),
};

let icon_name = match env::var("TRAY_ICON_NAME") {
Ok(name) => name,
_ => "rust-logo".to_string(),
};

indicator.set_icon_theme_path(icon_path.to_str().unwrap());
indicator.set_icon_full("rust-logo", "icon");
indicator.set_icon_full(&icon_name, "icon");
let mut m = gtk::Menu::new();
let mi = gtk::CheckMenuItem::with_label("Hello Rust!");
mi.connect_activate(|_| {
Expand Down
Binary file modified examples/rust-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ categories = [ "external-ffi-bindings" ]
gtk-sys = "0.15"
libloading = "0.7"
once_cell = "1.12"

[features]
default = [ "backcompat" ]
backcompat = [ ]
94 changes: 36 additions & 58 deletions sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,73 +8,51 @@ use gtk_sys::{
};
use libloading::*;
use once_cell::sync::Lazy;
use std::{os::raw::*, path::PathBuf, process::Command};
use std::os::raw::*;

pub static LIB: Lazy<Library> = Lazy::new(|| {
#[cfg(target_os = "linux")]
{
if let Some(appimage_path) = std::env::var_os("APPDIR") {
// validate that we're actually running on an AppImage
// an AppImage is mounted to `/$TEMPDIR/.mount_${appPrefix}${hash}`
// see https://github.com/AppImage/AppImageKit/blob/1681fd84dbe09c7d9b22e13cdb16ea601aa0ec47/src/runtime.c#L501
// note that it is safe to use `std::env::current_exe` here since we just loaded an AppImage.
let is_temp = std::env::current_exe()
.map(|p| {
p.display()
.to_string()
.starts_with(&format!("{}/.mount_", std::env::temp_dir().display()))
})
.unwrap_or(true);
let libayatana = unsafe { Library::new("libayatana-appindicator3.so.1") };
if let Ok(lib) = libayatana {
return lib;
}

if !is_temp {
panic!("`APPDIR` environment variable found but this application was not detected as an AppImage; this might be a security issue.");
}
let libappindicator = unsafe { Library::new("libappindicator3.so.1") };
if let Ok(lib) = libappindicator {
return lib;
}

let appimage_path = PathBuf::from(appimage_path);
let ayatana_target_path = appimage_path.join("usr/lib/libayatana-appindicator3.so");
let gtk_target_path = appimage_path.join("usr/lib/libappindicator3.so");
// Versions v0.7.1 and v0.7.2 relied exclusively on the .so files without .1 suffix.
// This is 'bad' because by convention that signals we don't care about ABI compatibility.
// However in weird cases (*ahum* Tauri bundled appimages) this .so file is the only one
// available. Using this feature flag allows them some time to fix this problem and bundle
// with the correct filename.
#[cfg(feature = "backcompat")]
{
let libayatana_compat = unsafe { Library::new("libayatana-appindicator3.so") };
if let Ok(lib) = libayatana_compat {
return lib;
}

if ayatana_target_path.exists() {
return unsafe { Library::new(ayatana_target_path).unwrap() };
} else if gtk_target_path.exists() {
return unsafe { Library::new(gtk_target_path).unwrap() };
}
let libappindicator_compat = unsafe { Library::new("libappindicator3.so") };
if let Ok(lib) = libappindicator_compat {
return lib;
}
}
// PKG_CONFIG_ALLOW_SYSTEM_LIBS=1 pkg-config --libs-only-L ayatana-appindicator3-0.1
let path = get_appindicator_library_path();
unsafe { Library::new(path).unwrap() }
});

/// Gets the path to the target appindicator library file (`.so` extension).
pub fn get_appindicator_library_path() -> PathBuf {
match get_library_path("ayatana-appindicator3-0.1") {
Some(p) => format!("{}/libayatana-appindicator3.so", p).into(),
None => match get_library_path("appindicator3-0.1") {
Some(p) => format!("{}/libappindicator3.so", p).into(),
None => panic!("Can't detect any appindicator library"),
},
panic!(
"Failed to load ayatana-appindicator3 or appindicator3 dynamic library\n{}\n{}\n{}\n{}",
libayatana.unwrap_err(),
libappindicator.unwrap_err(),
libayatana_compat.unwrap_err(),
libappindicator_compat.unwrap_err(),
);
}
}

/// Gets the folder in which a library is located using `pkg-config`.
pub fn get_library_path(name: &str) -> Option<String> {
let mut cmd = Command::new("pkg-config");
cmd.env("PKG_CONFIG_ALLOW_SYSTEM_LIBS", "1");
cmd.arg("--libs-only-L");
cmd.arg(name);
if let Ok(output) = cmd.output() {
if !output.stdout.is_empty() {
// output would be "-L/path/to/library\n"
let word = output.stdout[2..].to_vec();
return Some(String::from_utf8_lossy(&word).trim().to_string());
} else {
return None;
}
} else {
return None;
}
}
panic!(
"Failed to load ayatana-appindicator3 or appindicator3 dynamic library\n{}\n{}",
libayatana.unwrap_err(),
libappindicator.unwrap_err()
);
});

pub type guint32 = c_uint;
pub type gint64 = c_long;
Expand Down