mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
source code
This commit is contained in:
5
libs/systray-rs/.gitignore
vendored
Normal file
5
libs/systray-rs/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
|
||||
Cargo.lock
|
||||
/target/
|
||||
|
||||
32
libs/systray-rs/.travis.yml
Normal file
32
libs/systray-rs/.travis.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
language: rust
|
||||
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- build-essential
|
||||
- libgtk-3-dev
|
||||
- libappindicator3-dev
|
||||
- gcc-5
|
||||
|
||||
before_install: . ./ci/before_install.sh
|
||||
|
||||
script:
|
||||
- RUST_BACKTRACE=1 PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH cargo build --verbose
|
||||
- RUST_BACKTRACE=1 PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH cargo test --verbose
|
||||
|
||||
global_env:
|
||||
secure: O40C4FadE2C8yApgCbQNYmeWQuytrhu4W3a2HKRvGgB39LP0ysMU2UKXQIyZqlZUS9mP9qi5HYN+GTt83aE3Ac0eAwRqq+9zMjC2qMaiZ1JBSfCJI5wiiIXP0HpbsxXipG2Z21aqupVfu0HjNP4RVkaZ7ONKAeLAieI06+7VHbMPw6mcJd4Drv8VTyKn89VvB4lxKexLcURfagoic3fzeFKaIIVBSqGHiXrURbpD5tffOnzc5YFWxeGKTVFl8WqQVrRk2gnl/39UhSsOHGuSExw5GSxh+OaNHTiAkvOaSQLa05Y5mkNlHAsMyqg1mW3mI2xuzCQaFFT5G5JF7uxvZsa4GfROaEG8r1CZvpWxG2NtpupXvIC25nN+QQeeMZv5PHaxlk9OkG0k+2+z1Tu0Yd05x/o3+52YFo3geVwDmI3zx4Zgg9u9nIwGhdtzqbKV2fQNnKbNWVQH6D5M1DlBMYyY25jpkehcazqUbLsJXJFIoMkXhdkjTIpZg4w+CQ617WCnoDhXh6+Iqkw+iBBJJugaf2D6qBpXNiLZNJwbv2M5fj8uDsDtsUvjg56qBw+g+TeHJDKjzpEId/zFrAe4lmuFjN4/SlDk3n5xjZ5eY4PGRp1K8DGgeBQI5gyvHR3H7lm4GE2NCEvNILYFjpANZsiWwDepb2/rHvYNiLK+jhc=
|
||||
|
||||
env:
|
||||
- LLVM_VERSION=3.9 CLANG_VERSION=clang_3_9
|
||||
33
libs/systray-rs/CHANGELOG.md
Normal file
33
libs/systray-rs/CHANGELOG.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 0.4.0 (2020-02-15)
|
||||
|
||||
## Features
|
||||
|
||||
- Brought up to Rust 2018 (thanks to https://github.com/udoprog)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Updated libappindicator to compile on modern rust
|
||||
|
||||
# 0.3.0 (2018-04-28)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Update gtk so linux version will run again
|
||||
|
||||
# 0.2.0 (2017-05-04)
|
||||
|
||||
## Features
|
||||
|
||||
- Add Linux Support
|
||||
|
||||
# 0.1.1 (2017-02-28)
|
||||
|
||||
## Bugfixes
|
||||
|
||||
- Some cleanup and CI work
|
||||
|
||||
# 0.1.0 (2017-02-22)
|
||||
|
||||
## Features
|
||||
|
||||
- Basic Win32 systray support
|
||||
28
libs/systray-rs/Cargo.toml
Normal file
28
libs/systray-rs/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
||||
[package]
|
||||
name = "systray"
|
||||
version = "0.4.1"
|
||||
authors = ["Kyle Machulis <kyle@machul.is>"]
|
||||
description = "Rust library for making minimal cross-platform systray GUIs"
|
||||
license = "BSD-3-Clause"
|
||||
homepage = "http://github.com/qdot/systray-rs"
|
||||
repository = "https://github.com/qdot/systray-rs.git"
|
||||
readme = "README.md"
|
||||
keywords = ["gui"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
log= "0.4"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi= { version = "0.3", features = ["shellapi", "libloaderapi", "errhandlingapi", "impl-default"] }
|
||||
libc= "0.2"
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
gtk= "0.9"
|
||||
glib= "0.10"
|
||||
libappindicator= "0.5"
|
||||
|
||||
# [target.'cfg(target_os = "macos")'.dependencies]
|
||||
# objc="*"
|
||||
# cocoa="*"
|
||||
# core-foundation="*"
|
||||
28
libs/systray-rs/LICENSE.txt
Normal file
28
libs/systray-rs/LICENSE.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
Copyright (c) 2016, Kyle Machulis
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the project nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
58
libs/systray-rs/README.md
Normal file
58
libs/systray-rs/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# systray-rs
|
||||
|
||||
[](https://crates.io/crates/systray) [](https://crates.io/crates/systray)
|
||||
|
||||
[](https://travis-ci.org/qdot/systray-rs) [](https://ci.appveyor.com/project/qdot/systray-rs)
|
||||
|
||||
systray-rs is a Rust library that makes it easy for applications to
|
||||
have minimal UI in a platform specific way. It wraps the platform
|
||||
specific calls required to show an icon in the system tray, as well as
|
||||
add menu entries.
|
||||
|
||||
systray-rs is heavily influenced by
|
||||
[the systray library for the Go Language](https://github.com/getlantern/systray).
|
||||
|
||||
systray-rs currently supports:
|
||||
|
||||
- Linux GTK
|
||||
- Win32
|
||||
|
||||
Cocoa core still needed!
|
||||
|
||||
# License
|
||||
|
||||
systray-rs includes some code
|
||||
from [winapi-rs, by retep998](https://github.com/retep998/winapi-rs).
|
||||
This code is covered under the MIT license. This code will be removed
|
||||
once winapi-rs has a 0.3 crate available.
|
||||
|
||||
systray-rs is BSD licensed.
|
||||
|
||||
Copyright (c) 2016-2020, Nonpolynomial Labs, LLC
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the project nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
22
libs/systray-rs/appveyor.yml
Normal file
22
libs/systray-rs/appveyor.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
version: "0.1.0.{build}"
|
||||
environment:
|
||||
matrix:
|
||||
- TARGET: nightly-x86_64-pc-windows-msvc
|
||||
- TARGET: nightly-i686-pc-windows-msvc
|
||||
- TARGET: nightly-x86_64-pc-windows-gnu
|
||||
- TARGET: nightly-i686-pc-windows-gnu
|
||||
install:
|
||||
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe"
|
||||
- ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null
|
||||
- ps: $env:PATH="$env:PATH;C:\rust\bin"
|
||||
- rustc -vV
|
||||
- cargo -vV
|
||||
- erase rust-install.exe
|
||||
build_script:
|
||||
- cargo build
|
||||
# Skip packaging step while we're running off a local winapi build
|
||||
#- cargo package
|
||||
skip_commits:
|
||||
files:
|
||||
- README.md
|
||||
- .travis.yml
|
||||
39
libs/systray-rs/ci/before_install.sh
Normal file
39
libs/systray-rs/ci/before_install.sh
Normal file
@@ -0,0 +1,39 @@
|
||||
set -e
|
||||
pushd ~
|
||||
|
||||
# Workaround for Travis CI macOS bug (https://github.com/travis-ci/travis-ci/issues/6307)
|
||||
if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
|
||||
rvm get head || true
|
||||
fi
|
||||
|
||||
function llvm_version_triple() {
|
||||
if [ "$1" == "3.8" ]; then
|
||||
echo "3.8.0"
|
||||
elif [ "$1" == "3.9" ]; then
|
||||
echo "3.9.0"
|
||||
fi
|
||||
}
|
||||
|
||||
function llvm_download() {
|
||||
export LLVM_VERSION_TRIPLE=`llvm_version_triple ${LLVM_VERSION}`
|
||||
export LLVM=clang+llvm-${LLVM_VERSION_TRIPLE}-x86_64-$1
|
||||
|
||||
wget http://llvm.org/releases/${LLVM_VERSION_TRIPLE}/${LLVM}.tar.xz
|
||||
mkdir llvm
|
||||
tar -xf ${LLVM}.tar.xz -C llvm --strip-components=1
|
||||
|
||||
export LLVM_CONFIG_PATH=`pwd`/llvm/bin/llvm-config
|
||||
if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
|
||||
cp llvm/lib/libclang.dylib /usr/local/lib/libclang.dylib
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
|
||||
llvm_download linux-gnu-ubuntu-14.04
|
||||
else
|
||||
llvm_download apple-darwin
|
||||
fi
|
||||
|
||||
popd
|
||||
set +e
|
||||
43
libs/systray-rs/examples/systray-example.rs
Normal file
43
libs/systray-rs/examples/systray-example.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
#![windows_subsystem = "windows"]
|
||||
|
||||
//#[cfg(target_os = "windows")]
|
||||
fn main() -> Result<(), systray::Error> {
|
||||
let mut app;
|
||||
match systray::Application::new() {
|
||||
Ok(w) => app = w,
|
||||
Err(_) => panic!("Can't create window!"),
|
||||
}
|
||||
// w.set_icon_from_file(&"C:\\Users\\qdot\\code\\git-projects\\systray-rs\\resources\\rust.ico".to_string());
|
||||
// w.set_tooltip(&"Whatever".to_string());
|
||||
app.set_icon_from_file("/usr/share/gxkb/flags/ua.png")?;
|
||||
|
||||
app.add_menu_item("Print a thing", |_| {
|
||||
println!("Printing a thing!");
|
||||
Ok::<_, systray::Error>(())
|
||||
})?;
|
||||
|
||||
app.add_menu_item("Add Menu Item", |window| {
|
||||
window.add_menu_item("Interior item", |_| {
|
||||
println!("what");
|
||||
Ok::<_, systray::Error>(())
|
||||
})?;
|
||||
window.add_menu_separator()?;
|
||||
Ok::<_, systray::Error>(())
|
||||
})?;
|
||||
|
||||
app.add_menu_separator()?;
|
||||
|
||||
app.add_menu_item("Quit", |window| {
|
||||
window.quit();
|
||||
Ok::<_, systray::Error>(())
|
||||
})?;
|
||||
|
||||
println!("Waiting on message!");
|
||||
app.wait_for_message()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// #[cfg(not(target_os = "windows"))]
|
||||
// fn main() {
|
||||
// panic!("Not implemented on this platform!");
|
||||
// }
|
||||
BIN
libs/systray-rs/resources/rust.ico
Normal file
BIN
libs/systray-rs/resources/rust.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
28
libs/systray-rs/src/api/cocoa/mod.rs
Normal file
28
libs/systray-rs/src/api/cocoa/mod.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use crate::Error;
|
||||
use std;
|
||||
|
||||
pub struct Window {}
|
||||
|
||||
impl Window {
|
||||
pub fn new() -> Result<Window, Error> {
|
||||
Err(Error::NotImplementedError)
|
||||
}
|
||||
pub fn quit(&self) {
|
||||
unimplemented!()
|
||||
}
|
||||
pub fn set_tooltip(&self, _: &str) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
pub fn add_menu_item<F>(&self, _: &str, _: F) -> Result<u32, Error>
|
||||
where
|
||||
F: std::ops::Fn(&Window) -> () + 'static,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
pub fn wait_for_message(&mut self) {
|
||||
unimplemented!()
|
||||
}
|
||||
pub fn set_icon_from_buffer(&self, _: &[u8], _: u32, _: u32) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
182
libs/systray-rs/src/api/linux/mod.rs
Normal file
182
libs/systray-rs/src/api/linux/mod.rs
Normal file
@@ -0,0 +1,182 @@
|
||||
use crate::{Error, SystrayEvent};
|
||||
use glib;
|
||||
use gtk::{
|
||||
self, MenuShellExt, GtkMenuItemExt, WidgetExt
|
||||
};
|
||||
use libappindicator::{AppIndicator, AppIndicatorStatus};
|
||||
use std::{
|
||||
self,
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
sync::mpsc::{channel, Sender},
|
||||
thread,
|
||||
};
|
||||
|
||||
// Gtk specific struct that will live only in the Gtk thread, since a lot of the
|
||||
// base types involved don't implement Send (for good reason).
|
||||
pub struct GtkSystrayApp {
|
||||
menu: gtk::Menu,
|
||||
ai: RefCell<AppIndicator>,
|
||||
menu_items: RefCell<HashMap<u32, gtk::MenuItem>>,
|
||||
event_tx: Sender<SystrayEvent>,
|
||||
}
|
||||
|
||||
thread_local!(static GTK_STASH: RefCell<Option<GtkSystrayApp>> = RefCell::new(None));
|
||||
|
||||
pub struct MenuItemInfo {
|
||||
mid: u32,
|
||||
title: String,
|
||||
tooltip: String,
|
||||
disabled: bool,
|
||||
checked: bool,
|
||||
}
|
||||
|
||||
type Callback = Box<(Fn(&GtkSystrayApp) -> () + 'static)>;
|
||||
|
||||
// Convenience function to clean up thread local unwrapping
|
||||
fn run_on_gtk_thread<F>(f: F)
|
||||
where
|
||||
F: std::ops::Fn(&GtkSystrayApp) -> () + Send + 'static,
|
||||
{
|
||||
// Note this is glib, not gtk. Calling gtk::idle_add will panic us due to
|
||||
// being on different threads. glib::idle_add can run across threads.
|
||||
glib::idle_add(move || {
|
||||
GTK_STASH.with(|stash| {
|
||||
let stash = stash.borrow();
|
||||
let stash = stash.as_ref();
|
||||
if let Some(stash) = stash {
|
||||
f(stash);
|
||||
}
|
||||
});
|
||||
gtk::prelude::Continue(false)
|
||||
});
|
||||
}
|
||||
|
||||
impl GtkSystrayApp {
|
||||
pub fn new(event_tx: Sender<SystrayEvent>) -> Result<GtkSystrayApp, Error> {
|
||||
if let Err(e) = gtk::init() {
|
||||
return Err(Error::OsError(format!("{}", "Gtk init error!")));
|
||||
}
|
||||
let mut m = gtk::Menu::new();
|
||||
let mut ai = AppIndicator::new("", "");
|
||||
ai.set_status(AppIndicatorStatus::Active);
|
||||
ai.set_menu(&mut m);
|
||||
Ok(GtkSystrayApp {
|
||||
menu: m,
|
||||
ai: RefCell::new(ai),
|
||||
menu_items: RefCell::new(HashMap::new()),
|
||||
event_tx: event_tx,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn systray_menu_selected(&self, menu_id: u32) {
|
||||
self.event_tx
|
||||
.send(SystrayEvent {
|
||||
menu_index: menu_id as u32,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
||||
pub fn add_menu_separator(&self, item_idx: u32) {
|
||||
//let mut menu_items = self.menu_items.borrow_mut();
|
||||
let m = gtk::SeparatorMenuItem::new();
|
||||
self.menu.append(&m);
|
||||
//menu_items.insert(item_idx, m);
|
||||
self.menu.show_all();
|
||||
}
|
||||
|
||||
pub fn add_menu_entry(&self, item_idx: u32, item_name: &str) {
|
||||
let mut menu_items = self.menu_items.borrow_mut();
|
||||
if menu_items.contains_key(&item_idx) {
|
||||
let m: >k::MenuItem = menu_items.get(&item_idx).unwrap();
|
||||
m.set_label(item_name);
|
||||
self.menu.show_all();
|
||||
return;
|
||||
}
|
||||
let m = gtk::MenuItem::new_with_label(item_name);
|
||||
self.menu.append(&m);
|
||||
m.connect_activate(move |_| {
|
||||
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
|
||||
stash.systray_menu_selected(item_idx);
|
||||
});
|
||||
});
|
||||
menu_items.insert(item_idx, m);
|
||||
self.menu.show_all();
|
||||
}
|
||||
|
||||
pub fn set_icon_from_file(&self, file: &str) {
|
||||
let mut ai = self.ai.borrow_mut();
|
||||
ai.set_icon_full(file, "icon");
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
gtk_loop: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(event_tx: Sender<SystrayEvent>) -> Result<Window, Error> {
|
||||
let (tx, rx) = channel();
|
||||
let gtk_loop = thread::spawn(move || {
|
||||
GTK_STASH.with(|stash| match GtkSystrayApp::new(event_tx) {
|
||||
Ok(data) => {
|
||||
(*stash.borrow_mut()) = Some(data);
|
||||
tx.send(Ok(()));
|
||||
}
|
||||
Err(e) => {
|
||||
tx.send(Err(e));
|
||||
return;
|
||||
}
|
||||
});
|
||||
gtk::main();
|
||||
});
|
||||
match rx.recv().unwrap() {
|
||||
Ok(()) => Ok(Window {
|
||||
gtk_loop: Some(gtk_loop),
|
||||
}),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_menu_entry(&self, item_idx: u32, item_name: &str) -> Result<(), Error> {
|
||||
let n = item_name.to_owned().clone();
|
||||
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
|
||||
stash.add_menu_entry(item_idx, &n);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_menu_separator(&self, item_idx: u32) -> Result<(), Error> {
|
||||
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
|
||||
stash.add_menu_separator(item_idx);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_icon_from_file(&self, file: &str) -> Result<(), Error> {
|
||||
let n = file.to_owned().clone();
|
||||
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
|
||||
stash.set_icon_from_file(&n);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_icon_from_resource(&self, resource: &str) -> Result<(), Error> {
|
||||
panic!("Not implemented on this platform!");
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_tooltip(&self, tooltip: &str) -> Result<(), Error> {
|
||||
panic!("Not implemented on this platform!");
|
||||
}
|
||||
|
||||
pub fn quit(&self) {
|
||||
glib::idle_add(|| {
|
||||
gtk::main_quit();
|
||||
glib::Continue(false)
|
||||
});
|
||||
}
|
||||
}
|
||||
11
libs/systray-rs/src/api/mod.rs
Normal file
11
libs/systray-rs/src/api/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
#[path = "win32/mod.rs"]
|
||||
pub mod api;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[path = "linux/mod.rs"]
|
||||
pub mod api;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "cocoa/mod.rs"]
|
||||
pub mod api;
|
||||
460
libs/systray-rs/src/api/win32/mod.rs
Normal file
460
libs/systray-rs/src/api/win32/mod.rs
Normal file
@@ -0,0 +1,460 @@
|
||||
use crate::{Error, SystrayEvent};
|
||||
use std;
|
||||
use std::cell::RefCell;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::thread;
|
||||
use winapi::{
|
||||
ctypes::{c_ulong, c_ushort},
|
||||
shared::{
|
||||
basetsd::ULONG_PTR,
|
||||
guiddef::GUID,
|
||||
minwindef::{DWORD, HINSTANCE, LPARAM, LRESULT, PBYTE, TRUE, UINT, WPARAM},
|
||||
ntdef::LPCWSTR,
|
||||
windef::{HBITMAP, HBRUSH, HICON, HMENU, HWND, POINT},
|
||||
},
|
||||
um::{
|
||||
errhandlingapi, libloaderapi,
|
||||
shellapi::{
|
||||
self, NIF_ICON, NIF_MESSAGE, NIF_TIP, NIM_ADD, NIM_DELETE, NIM_MODIFY, NOTIFYICONDATAW,
|
||||
},
|
||||
winuser::{
|
||||
self, CW_USEDEFAULT, IMAGE_ICON, LR_DEFAULTCOLOR, LR_LOADFROMFILE, MENUINFO,
|
||||
MENUITEMINFOW, MFT_SEPARATOR, MFT_STRING, MIIM_FTYPE, MIIM_ID, MIIM_STATE, MIIM_STRING,
|
||||
MIM_APPLYTOSUBMENUS, MIM_STYLE, MNS_NOTIFYBYPOS, WM_DESTROY, WM_USER, WNDCLASSW,
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Got this idea from glutin. Yay open source! Boo stupid winproc! Even more boo
|
||||
// doing SetLongPtr tho.
|
||||
thread_local!(static WININFO_STASH: RefCell<Option<WindowsLoopData>> = RefCell::new(None));
|
||||
|
||||
fn to_wstring(str: &str) -> Vec<u16> {
|
||||
OsStr::new(str)
|
||||
.encode_wide()
|
||||
.chain(Some(0).into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WindowInfo {
|
||||
pub hwnd: HWND,
|
||||
pub hinstance: HINSTANCE,
|
||||
pub hmenu: HMENU,
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowInfo {}
|
||||
unsafe impl Sync for WindowInfo {}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WindowsLoopData {
|
||||
pub info: WindowInfo,
|
||||
pub tx: Sender<SystrayEvent>,
|
||||
}
|
||||
|
||||
unsafe fn get_win_os_error(msg: &str) -> Error {
|
||||
Error::OsError(format!("{}: {}", &msg, errhandlingapi::GetLastError()))
|
||||
}
|
||||
|
||||
unsafe extern "system" fn window_proc(
|
||||
h_wnd: HWND,
|
||||
msg: UINT,
|
||||
w_param: WPARAM,
|
||||
l_param: LPARAM,
|
||||
) -> LRESULT {
|
||||
if msg == winuser::WM_MENUCOMMAND {
|
||||
WININFO_STASH.with(|stash| {
|
||||
let stash = stash.borrow();
|
||||
let stash = stash.as_ref();
|
||||
if let Some(stash) = stash {
|
||||
let menu_id = winuser::GetMenuItemID(stash.info.hmenu, w_param as i32) as i32;
|
||||
if menu_id != -1 {
|
||||
stash
|
||||
.tx
|
||||
.send(SystrayEvent {
|
||||
menu_index: menu_id as u32,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if msg == WM_USER + 1 {
|
||||
if l_param as UINT == winuser::WM_LBUTTONUP || l_param as UINT == winuser::WM_RBUTTONUP {
|
||||
let mut p = POINT { x: 0, y: 0 };
|
||||
if winuser::GetCursorPos(&mut p as *mut POINT) == 0 {
|
||||
return 1;
|
||||
}
|
||||
winuser::SetForegroundWindow(h_wnd);
|
||||
WININFO_STASH.with(|stash| {
|
||||
let stash = stash.borrow();
|
||||
let stash = stash.as_ref();
|
||||
if let Some(stash) = stash {
|
||||
winuser::TrackPopupMenu(
|
||||
stash.info.hmenu,
|
||||
0,
|
||||
p.x,
|
||||
p.y,
|
||||
(winuser::TPM_BOTTOMALIGN | winuser::TPM_LEFTALIGN) as i32,
|
||||
h_wnd,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if msg == winuser::WM_DESTROY {
|
||||
winuser::PostQuitMessage(0);
|
||||
}
|
||||
return winuser::DefWindowProcW(h_wnd, msg, w_param, l_param);
|
||||
}
|
||||
|
||||
fn get_nid_struct(hwnd: &HWND) -> NOTIFYICONDATAW {
|
||||
NOTIFYICONDATAW {
|
||||
cbSize: std::mem::size_of::<NOTIFYICONDATAW>() as DWORD,
|
||||
hWnd: *hwnd,
|
||||
uID: 0x1 as UINT,
|
||||
uFlags: 0 as UINT,
|
||||
uCallbackMessage: 0 as UINT,
|
||||
hIcon: 0 as HICON,
|
||||
szTip: [0 as u16; 128],
|
||||
dwState: 0 as DWORD,
|
||||
dwStateMask: 0 as DWORD,
|
||||
szInfo: [0 as u16; 256],
|
||||
u: Default::default(),
|
||||
szInfoTitle: [0 as u16; 64],
|
||||
dwInfoFlags: 0 as UINT,
|
||||
guidItem: GUID {
|
||||
Data1: 0 as c_ulong,
|
||||
Data2: 0 as c_ushort,
|
||||
Data3: 0 as c_ushort,
|
||||
Data4: [0; 8],
|
||||
},
|
||||
hBalloonIcon: 0 as HICON,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_menu_item_struct() -> MENUITEMINFOW {
|
||||
MENUITEMINFOW {
|
||||
cbSize: std::mem::size_of::<MENUITEMINFOW>() as UINT,
|
||||
fMask: 0 as UINT,
|
||||
fType: 0 as UINT,
|
||||
fState: 0 as UINT,
|
||||
wID: 0 as UINT,
|
||||
hSubMenu: 0 as HMENU,
|
||||
hbmpChecked: 0 as HBITMAP,
|
||||
hbmpUnchecked: 0 as HBITMAP,
|
||||
dwItemData: 0 as ULONG_PTR,
|
||||
dwTypeData: std::ptr::null_mut(),
|
||||
cch: 0 as u32,
|
||||
hbmpItem: 0 as HBITMAP,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init_window() -> Result<WindowInfo, Error> {
|
||||
let class_name = to_wstring("my_window");
|
||||
let hinstance: HINSTANCE = libloaderapi::GetModuleHandleA(std::ptr::null_mut());
|
||||
let wnd = WNDCLASSW {
|
||||
style: 0,
|
||||
lpfnWndProc: Some(window_proc),
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hInstance: 0 as HINSTANCE,
|
||||
hIcon: winuser::LoadIconW(0 as HINSTANCE, winuser::IDI_APPLICATION),
|
||||
hCursor: winuser::LoadCursorW(0 as HINSTANCE, winuser::IDI_APPLICATION),
|
||||
hbrBackground: 16 as HBRUSH,
|
||||
lpszMenuName: 0 as LPCWSTR,
|
||||
lpszClassName: class_name.as_ptr(),
|
||||
};
|
||||
if winuser::RegisterClassW(&wnd) == 0 {
|
||||
return Err(get_win_os_error("Error creating window class"));
|
||||
}
|
||||
let hwnd = winuser::CreateWindowExW(
|
||||
0,
|
||||
class_name.as_ptr(),
|
||||
to_wstring("rust_systray_window").as_ptr(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
0,
|
||||
CW_USEDEFAULT,
|
||||
0,
|
||||
0 as HWND,
|
||||
0 as HMENU,
|
||||
0 as HINSTANCE,
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
if hwnd == std::ptr::null_mut() {
|
||||
return Err(get_win_os_error("Error creating window"));
|
||||
}
|
||||
let mut nid = get_nid_struct(&hwnd);
|
||||
nid.uID = 0x1;
|
||||
nid.uFlags = NIF_MESSAGE;
|
||||
nid.uCallbackMessage = WM_USER + 1;
|
||||
if shellapi::Shell_NotifyIconW(NIM_ADD, &mut nid as *mut NOTIFYICONDATAW) == 0 {
|
||||
return Err(get_win_os_error("Error adding menu icon"));
|
||||
}
|
||||
// Setup menu
|
||||
let hmenu = winuser::CreatePopupMenu();
|
||||
let m = MENUINFO {
|
||||
cbSize: std::mem::size_of::<MENUINFO>() as DWORD,
|
||||
fMask: MIM_APPLYTOSUBMENUS | MIM_STYLE,
|
||||
dwStyle: MNS_NOTIFYBYPOS,
|
||||
cyMax: 0 as UINT,
|
||||
hbrBack: 0 as HBRUSH,
|
||||
dwContextHelpID: 0 as DWORD,
|
||||
dwMenuData: 0 as ULONG_PTR,
|
||||
};
|
||||
if winuser::SetMenuInfo(hmenu, &m as *const MENUINFO) == 0 {
|
||||
return Err(get_win_os_error("Error setting up menu"));
|
||||
}
|
||||
|
||||
Ok(WindowInfo {
|
||||
hwnd: hwnd,
|
||||
hmenu: hmenu,
|
||||
hinstance: hinstance,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn run_loop() {
|
||||
log::debug!("Running windows loop");
|
||||
// Run message loop
|
||||
let mut msg = winuser::MSG {
|
||||
hwnd: 0 as HWND,
|
||||
message: 0 as UINT,
|
||||
wParam: 0 as WPARAM,
|
||||
lParam: 0 as LPARAM,
|
||||
time: 0 as DWORD,
|
||||
pt: POINT { x: 0, y: 0 },
|
||||
};
|
||||
loop {
|
||||
winuser::GetMessageW(&mut msg, 0 as HWND, 0, 0);
|
||||
if msg.message == winuser::WM_QUIT {
|
||||
break;
|
||||
}
|
||||
winuser::TranslateMessage(&mut msg);
|
||||
winuser::DispatchMessageW(&mut msg);
|
||||
}
|
||||
log::debug!("Leaving windows run loop");
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
info: WindowInfo,
|
||||
windows_loop: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(event_tx: Sender<SystrayEvent>) -> Result<Window, Error> {
|
||||
let (tx, rx) = channel();
|
||||
let windows_loop = thread::spawn(move || {
|
||||
unsafe {
|
||||
let i = init_window();
|
||||
let k;
|
||||
match i {
|
||||
Ok(j) => {
|
||||
tx.send(Ok(j.clone())).ok();
|
||||
k = j;
|
||||
}
|
||||
Err(e) => {
|
||||
// If creation didn't work, return out of the thread.
|
||||
tx.send(Err(e)).ok();
|
||||
return;
|
||||
}
|
||||
};
|
||||
WININFO_STASH.with(|stash| {
|
||||
let data = WindowsLoopData {
|
||||
info: k,
|
||||
tx: event_tx,
|
||||
};
|
||||
(*stash.borrow_mut()) = Some(data);
|
||||
});
|
||||
run_loop();
|
||||
}
|
||||
});
|
||||
let info = match rx.recv().unwrap() {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let w = Window {
|
||||
info: info,
|
||||
windows_loop: Some(windows_loop),
|
||||
};
|
||||
Ok(w)
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
unsafe {
|
||||
winuser::PostMessageW(self.info.hwnd, WM_DESTROY, 0 as WPARAM, 0 as LPARAM);
|
||||
}
|
||||
if let Some(t) = self.windows_loop.take() {
|
||||
t.join().ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_tooltip(&self, tooltip: &str) -> Result<(), Error> {
|
||||
// Add Tooltip
|
||||
log::debug!("Setting tooltip to {}", tooltip);
|
||||
// Gross way to convert String to [i8; 128]
|
||||
// TODO: Clean up conversion, test for length so we don't panic at runtime
|
||||
let tt = tooltip.as_bytes().clone();
|
||||
let mut nid = get_nid_struct(&self.info.hwnd);
|
||||
for i in 0..tt.len() {
|
||||
nid.szTip[i] = tt[i] as u16;
|
||||
}
|
||||
nid.uFlags = NIF_TIP;
|
||||
unsafe {
|
||||
if shellapi::Shell_NotifyIconW(NIM_MODIFY, &mut nid as *mut NOTIFYICONDATAW) == 0 {
|
||||
return Err(get_win_os_error("Error setting tooltip"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_menu_entry(&self, item_idx: u32, item_name: &str) -> Result<(), Error> {
|
||||
let mut st = to_wstring(item_name);
|
||||
let mut item = get_menu_item_struct();
|
||||
item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE;
|
||||
item.fType = MFT_STRING;
|
||||
item.wID = item_idx;
|
||||
item.dwTypeData = st.as_mut_ptr();
|
||||
item.cch = (item_name.len() * 2) as u32;
|
||||
unsafe {
|
||||
if winuser::InsertMenuItemW(self.info.hmenu, item_idx, 1, &item as *const MENUITEMINFOW)
|
||||
== 0
|
||||
{
|
||||
return Err(get_win_os_error("Error inserting menu item"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_menu_entry(&self, item_idx: u32) {
|
||||
unsafe { winuser::RemoveMenu(self.info.hmenu, item_idx, 1 as _); }
|
||||
}
|
||||
|
||||
pub fn add_menu_separator(&self, item_idx: u32) -> Result<(), Error> {
|
||||
let mut item = get_menu_item_struct();
|
||||
item.fMask = MIIM_FTYPE;
|
||||
item.fType = MFT_SEPARATOR;
|
||||
item.wID = item_idx;
|
||||
unsafe {
|
||||
if winuser::InsertMenuItemW(self.info.hmenu, item_idx, 1, &item as *const MENUITEMINFOW)
|
||||
== 0
|
||||
{
|
||||
return Err(get_win_os_error("Error inserting separator"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_icon(&self, icon: HICON) -> Result<(), Error> {
|
||||
unsafe {
|
||||
let mut nid = get_nid_struct(&self.info.hwnd);
|
||||
nid.uFlags = NIF_ICON;
|
||||
nid.hIcon = icon;
|
||||
if shellapi::Shell_NotifyIconW(NIM_MODIFY, &mut nid as *mut NOTIFYICONDATAW) == 0 {
|
||||
return Err(get_win_os_error("Error setting icon"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_icon_from_resource(&self, resource_name: &str) -> Result<(), Error> {
|
||||
let icon;
|
||||
unsafe {
|
||||
icon = winuser::LoadImageW(
|
||||
self.info.hinstance,
|
||||
to_wstring(&resource_name).as_ptr(),
|
||||
IMAGE_ICON,
|
||||
64,
|
||||
64,
|
||||
0,
|
||||
) as HICON;
|
||||
if icon == std::ptr::null_mut() as HICON {
|
||||
return Err(get_win_os_error("Error setting icon from resource"));
|
||||
}
|
||||
}
|
||||
self.set_icon(icon)
|
||||
}
|
||||
|
||||
pub fn set_icon_from_file(&self, icon_file: &str) -> Result<(), Error> {
|
||||
let wstr_icon_file = to_wstring(&icon_file);
|
||||
let hicon;
|
||||
unsafe {
|
||||
hicon = winuser::LoadImageW(
|
||||
std::ptr::null_mut() as HINSTANCE,
|
||||
wstr_icon_file.as_ptr(),
|
||||
IMAGE_ICON,
|
||||
64,
|
||||
64,
|
||||
LR_LOADFROMFILE,
|
||||
) as HICON;
|
||||
if hicon == std::ptr::null_mut() as HICON {
|
||||
return Err(get_win_os_error("Error setting icon from file"));
|
||||
}
|
||||
}
|
||||
self.set_icon(hicon)
|
||||
}
|
||||
|
||||
pub fn set_icon_from_buffer(
|
||||
&self,
|
||||
buffer: &[u8],
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), Error> {
|
||||
let offset = unsafe {
|
||||
winuser::LookupIconIdFromDirectoryEx(
|
||||
buffer.as_ptr() as PBYTE,
|
||||
TRUE,
|
||||
width as i32,
|
||||
height as i32,
|
||||
LR_DEFAULTCOLOR,
|
||||
)
|
||||
};
|
||||
|
||||
if offset != 0 {
|
||||
let icon_data = &buffer[offset as usize..];
|
||||
let hicon = unsafe {
|
||||
winuser::CreateIconFromResourceEx(
|
||||
icon_data.as_ptr() as PBYTE,
|
||||
icon_data.len() as u32,
|
||||
TRUE,
|
||||
0x30000,
|
||||
width as i32,
|
||||
height as i32,
|
||||
LR_DEFAULTCOLOR,
|
||||
)
|
||||
};
|
||||
|
||||
if hicon == std::ptr::null_mut() as HICON {
|
||||
return Err(unsafe { get_win_os_error("Cannot load icon from the buffer") });
|
||||
}
|
||||
|
||||
self.set_icon(hicon)
|
||||
} else {
|
||||
Err(unsafe { get_win_os_error("Error setting icon from buffer") })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) -> Result<(), Error> {
|
||||
unsafe {
|
||||
let mut nid = get_nid_struct(&self.info.hwnd);
|
||||
nid.uFlags = NIF_ICON;
|
||||
if shellapi::Shell_NotifyIconW(NIM_DELETE, &mut nid as *mut NOTIFYICONDATAW) == 0 {
|
||||
return Err(get_win_os_error("Error deleting icon from menu"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
self.shutdown().ok();
|
||||
}
|
||||
}
|
||||
192
libs/systray-rs/src/lib.rs
Normal file
192
libs/systray-rs/src/lib.rs
Normal file
@@ -0,0 +1,192 @@
|
||||
// Systray Lib
|
||||
pub mod api;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
error, fmt,
|
||||
sync::mpsc::{channel, Receiver},
|
||||
};
|
||||
|
||||
type BoxedError = Box<dyn error::Error + Send + Sync + 'static>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
OsError(String),
|
||||
NotImplementedError,
|
||||
UnknownError,
|
||||
Error(BoxedError),
|
||||
}
|
||||
|
||||
impl From<BoxedError> for Error {
|
||||
fn from(value: BoxedError) -> Self {
|
||||
Error::Error(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystrayEvent {
|
||||
menu_index: u32,
|
||||
}
|
||||
|
||||
impl error::Error for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
use self::Error::*;
|
||||
|
||||
match *self {
|
||||
OsError(ref err_str) => write!(f, "OsError: {}", err_str),
|
||||
NotImplementedError => write!(f, "Functionality is not implemented yet"),
|
||||
UnknownError => write!(f, "Unknown error occurrred"),
|
||||
Error(ref e) => write!(f, "Error: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Application {
|
||||
window: api::api::Window,
|
||||
menu_idx: u32,
|
||||
callback: HashMap<u32, Callback>,
|
||||
// Each platform-specific window module will set up its own thread for
|
||||
// dealing with the OS main loop. Use this channel for receiving events from
|
||||
// that thread.
|
||||
rx: Receiver<SystrayEvent>,
|
||||
timer: Option<(std::time::Duration, Callback)>,
|
||||
}
|
||||
|
||||
type Callback =
|
||||
Box<(dyn FnMut(&mut Application) -> Result<(), BoxedError> + Send + Sync + 'static)>;
|
||||
|
||||
fn make_callback<F, E>(mut f: F) -> Callback
|
||||
where
|
||||
F: FnMut(&mut Application) -> Result<(), E> + Send + Sync + 'static,
|
||||
E: error::Error + Send + Sync + 'static,
|
||||
{
|
||||
Box::new(move |a: &mut Application| match f(a) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => Err(Box::new(e) as BoxedError),
|
||||
}) as Callback
|
||||
}
|
||||
|
||||
impl Application {
|
||||
pub fn new() -> Result<Application, Error> {
|
||||
let (event_tx, event_rx) = channel();
|
||||
match api::api::Window::new(event_tx) {
|
||||
Ok(w) => Ok(Application {
|
||||
window: w,
|
||||
menu_idx: 0,
|
||||
callback: HashMap::new(),
|
||||
rx: event_rx,
|
||||
timer: None,
|
||||
}),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_timer<F, E>(
|
||||
&mut self,
|
||||
interval: std::time::Duration,
|
||||
callback: F,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
F: FnMut(&mut Application) -> Result<(), E> + Send + Sync + 'static,
|
||||
E: error::Error + Send + Sync + 'static,
|
||||
{
|
||||
self.timer = Some((interval, make_callback(callback)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_menu_item<F, E>(&mut self, item_name: &str, f: F) -> Result<u32, Error>
|
||||
where
|
||||
F: FnMut(&mut Application) -> Result<(), E> + Send + Sync + 'static,
|
||||
E: error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let idx = self.menu_idx;
|
||||
if let Err(e) = self.window.add_menu_entry(idx, item_name) {
|
||||
return Err(e);
|
||||
}
|
||||
self.callback.insert(idx, make_callback(f));
|
||||
self.menu_idx += 1;
|
||||
Ok(idx)
|
||||
}
|
||||
|
||||
pub fn remove_menu_item(&mut self, pos: u32) {
|
||||
self.window.remove_menu_entry(pos);
|
||||
self.callback.remove(&pos);
|
||||
}
|
||||
|
||||
pub fn add_menu_separator(&mut self) -> Result<u32, Error> {
|
||||
let idx = self.menu_idx;
|
||||
if let Err(e) = self.window.add_menu_separator(idx) {
|
||||
return Err(e);
|
||||
}
|
||||
self.menu_idx += 1;
|
||||
Ok(idx)
|
||||
}
|
||||
|
||||
pub fn set_icon_from_file(&self, file: &str) -> Result<(), Error> {
|
||||
self.window.set_icon_from_file(file)
|
||||
}
|
||||
|
||||
pub fn set_icon_from_resource(&self, resource: &str) -> Result<(), Error> {
|
||||
self.window.set_icon_from_resource(resource)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn set_icon_from_buffer(
|
||||
&self,
|
||||
buffer: &[u8],
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(), Error> {
|
||||
self.window.set_icon_from_buffer(buffer, width, height)
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) -> Result<(), Error> {
|
||||
self.window.shutdown()
|
||||
}
|
||||
|
||||
pub fn set_tooltip(&self, tooltip: &str) -> Result<(), Error> {
|
||||
self.window.set_tooltip(tooltip)
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
self.window.quit()
|
||||
}
|
||||
|
||||
pub fn wait_for_message(&mut self) -> Result<(), Error> {
|
||||
loop {
|
||||
let mut msg = None;
|
||||
if let Some((interval, _)) = self.timer.as_ref() {
|
||||
match self.rx.recv_timeout(interval.clone()) {
|
||||
Ok(m) => msg = Some(m),
|
||||
Err(_) => {}
|
||||
}
|
||||
} else {
|
||||
match self.rx.recv() {
|
||||
Ok(m) => msg = Some(m),
|
||||
Err(_) => {
|
||||
self.quit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(msg) = msg {
|
||||
if let Some(mut f) = self.callback.remove(&msg.menu_index) {
|
||||
f(self)?;
|
||||
self.callback.insert(msg.menu_index, f);
|
||||
}
|
||||
} else if let Some((interval, mut callback)) = self.timer.take() {
|
||||
callback(self)?;
|
||||
self.timer = Some((interval, callback));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Application {
|
||||
fn drop(&mut self) {
|
||||
self.shutdown().ok();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user