├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE.md ├── README.md ├── appveyor.yml ├── examples ├── Cargo.toml ├── hello_world.rs ├── hello_world_linux.png ├── hello_world_macos.png └── hello_world_windows.png └── src ├── common.rs ├── lib.rs ├── linux.rs ├── macos.rs └── windows.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | 5 | language: rust 6 | rust: 7 | - stable 8 | 9 | addons: 10 | apt: 11 | packages: 12 | - libgtk-3-dev 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "msgbox" 3 | version = "0.7.0" 4 | authors = ["Jang Ryeol "] 5 | description = "A multi-platform message box modal with a OK button, which runs synchronously." 6 | repository = "https://github.com/bekker/msgbox-rs" 7 | 8 | keywords = ["msgbox", "gui", "gtk"] 9 | license = "MIT" 10 | 11 | [dependencies] 12 | thiserror = "1.0.30" 13 | 14 | 15 | [target.'cfg(not(any(target_os = "windows", target_os = "macos")))'.dependencies] 16 | gtk = "0.15.3" 17 | glib = "0.15.5" 18 | 19 | [target.'cfg(target_os = "windows")'.dependencies] 20 | winapi = { version = "0.3.9", features = ["winuser"] } 21 | 22 | [target.'cfg(target_os = "macos")'.dependencies] 23 | cocoa = "0.24.0" 24 | objc = "0.2.7" 25 | 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jang Ryeol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository is archived 2 | - Please use https://github.com/native-dialog-rs/native-dialog-rs instead. 3 | 4 | # msgbox-rs 5 | 6 | | OS | Build Status | 7 | | -- | ----- | 8 | | Linux & OS X | [![Build Status](https://travis-ci.com/bekker/msgbox-rs.svg?branch=master)](https://travis-ci.com/bekker/msgbox-rs) | 9 | | Windows | [![Build status](https://ci.appveyor.com/api/projects/status/mtqq6smkg9lrteoc?svg=true)](https://ci.appveyor.com/project/bekker/msgbox-rs) | 10 | 11 | 12 | ```rust 13 | extern crate msgbox; 14 | 15 | use msgbox::IconType; 16 | 17 | fn main() { 18 | msgbox::create("Hello Title", "Hello World!", IconType::Info); 19 | } 20 | ``` 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Simple, cross-platform message box GUI library. 29 | 30 | All it does is to show a message box modal with an OK button, which runs synchronously. 31 | 32 | It supports multi-platform, and maintains separate dependencies per platform, thus light-weight. 33 | 34 | Example use case is to show a modal when an error occurs in OpenGL applications. 35 | 36 | - Synchronous Message Modal 37 | - Multi-platform (Linux GTK3+, Windows and OS X) 38 | - Light-weight 39 | 40 | ## Platform support 41 | * Linux with GTK 3+ (Tested on Ubuntu Gnome 16.04) 42 | * Windows (Tested on Windows 8.1 and 10) 43 | * OS X (Tested on MacOS 10.13.3 High Sierra) 44 | 45 | ## Dev Requirements 46 | 47 | ### Linux 48 | * `libgtk-3-dev` for apt 49 | * `gtk3-devel` for yum 50 | 51 | ### Windows 52 | * Windows version compatible with [winapi](https://github.com/retep998/winapi-rs) 53 | 54 | ### OS X 55 | * Tested on High Sierra 10.13.3, but it should work on 10.3+ 56 | 57 | ## License 58 | Distributed under MIT License 59 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - TARGET: x86_64-pc-windows-msvc 4 | - TARGET: i686-pc-windows-msvc 5 | install: 6 | - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-nightly-${env:TARGET}.exe" 7 | - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust" 8 | - SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin 9 | - rustc -V 10 | - cargo -V 11 | 12 | build: false 13 | 14 | test_script: 15 | - cargo test --verbose -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "example" 3 | version = "0.1.0" 4 | 5 | license = "MIT" 6 | 7 | [dependencies] 8 | msgbox = { path = ".." } 9 | 10 | [[bin]] 11 | name = "msgbox-example" 12 | path = "hello_world.rs" 13 | -------------------------------------------------------------------------------- /examples/hello_world.rs: -------------------------------------------------------------------------------- 1 | extern crate msgbox; 2 | 3 | use msgbox::common::MsgBoxError; 4 | use msgbox::IconType; 5 | 6 | fn main() { 7 | open_window("Hello Title", "Hello World!", IconType::Info); 8 | open_window( 9 | "Error", 10 | "Error occured at hello_world.rs:5.\nTerminating..", 11 | IconType::Error, 12 | ); 13 | } 14 | 15 | fn open_window(title: &str, content: &str, icon_type: IconType) { 16 | match msgbox::create(title, content, icon_type) { 17 | Ok(()) => (), 18 | Err(MsgBoxError::Create(_)) => { 19 | eprintln!( 20 | "Creation error: (type: {}, title: \"{}\", content: \"{}\")", 21 | icon_type, title, content 22 | ); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/hello_world_linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekker/msgbox-rs/e3ead016f2d2024fbe92bdbeec97147f7556b8b7/examples/hello_world_linux.png -------------------------------------------------------------------------------- /examples/hello_world_macos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekker/msgbox-rs/e3ead016f2d2024fbe92bdbeec97147f7556b8b7/examples/hello_world_macos.png -------------------------------------------------------------------------------- /examples/hello_world_windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bekker/msgbox-rs/e3ead016f2d2024fbe92bdbeec97147f7556b8b7/examples/hello_world_windows.png -------------------------------------------------------------------------------- /src/common.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] 2 | use glib::error::BoolError; 3 | use std::fmt; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub enum IconType { 7 | Error, 8 | Info, 9 | None, 10 | } 11 | 12 | impl fmt::Display for IconType { 13 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 14 | fmt::Debug::fmt(self, f) 15 | } 16 | } 17 | 18 | #[derive(thiserror::Error, Debug)] 19 | pub enum MsgBoxError { 20 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] 21 | #[error("failed to create a message box")] 22 | Create(#[from] BoolError), 23 | #[cfg(any(target_os = "windows", target_os = "macos"))] 24 | #[error("failed to create a message box")] 25 | Create(()), 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate thiserror; 2 | 3 | pub mod common; 4 | pub use common::{IconType, MsgBoxError}; 5 | 6 | /** 7 | * GTK+3 (Default) 8 | */ 9 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] 10 | extern crate gtk; 11 | 12 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] 13 | extern crate glib; 14 | 15 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] 16 | mod linux; 17 | 18 | #[cfg(not(any(target_os = "windows", target_os = "macos")))] 19 | pub use linux::*; 20 | 21 | /** 22 | * Cocoa 23 | */ 24 | #[cfg(target_os = "macos")] 25 | #[macro_use] 26 | extern crate objc; 27 | 28 | #[cfg(target_os = "macos")] 29 | extern crate cocoa; 30 | 31 | #[cfg(target_os = "macos")] 32 | mod macos; 33 | 34 | #[cfg(target_os = "macos")] 35 | pub use macos::*; 36 | 37 | /** 38 | * WinAPI 39 | */ 40 | #[cfg(target_os = "windows")] 41 | extern crate winapi; 42 | 43 | #[cfg(target_os = "windows")] 44 | mod windows; 45 | #[cfg(target_os = "windows")] 46 | pub use windows::*; 47 | -------------------------------------------------------------------------------- /src/linux.rs: -------------------------------------------------------------------------------- 1 | use gtk; 2 | use gtk::prelude::*; 3 | use gtk::{ButtonsType, DialogFlags, MessageDialog, MessageType}; 4 | 5 | use common::{IconType, MsgBoxError}; 6 | 7 | #[derive(thiserror::Error, Debug)] 8 | pub enum GtkError { 9 | #[error("failed to initialize GTK")] 10 | Init, 11 | } 12 | 13 | pub fn create( 14 | title: &str, 15 | content: &str, 16 | icon_type: IconType, 17 | ) -> std::result::Result<(), MsgBoxError> { 18 | gtk::init()?; 19 | 20 | let message_type = match icon_type { 21 | IconType::Error => MessageType::Error, 22 | IconType::Info => MessageType::Info, 23 | IconType::None => MessageType::Other, 24 | }; 25 | 26 | let dialog = MessageDialog::new( 27 | None::<>k::Window>, 28 | DialogFlags::empty(), 29 | message_type, 30 | ButtonsType::Ok, 31 | content, 32 | ); 33 | dialog.set_title(title); 34 | dialog.set_modal(true); 35 | dialog.set_decorated(true); 36 | dialog.set_keep_above(true); 37 | dialog.show(); 38 | dialog.connect_response(move |dialog, _| { 39 | unsafe { dialog.destroy() }; 40 | gtk::main_quit(); 41 | Inhibit(false); 42 | }); 43 | gtk::main(); 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /src/macos.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | #![allow(non_snake_case)] 3 | 4 | use cocoa::base::{id, nil}; 5 | use cocoa::foundation::NSString; 6 | 7 | use common::{IconType, MsgBoxError}; 8 | 9 | /** 10 | * cocoa-rs doesn't implement NSAlert yet (0.14.0) 11 | * Then implement it! 12 | * Someone would stub and implement all methods for NSAlert, and make it to the upstream? 13 | */ 14 | 15 | /** 16 | * NSAlert.Style 17 | * https://developer.apple.com/documentation/appkit/nsalert.style 18 | */ 19 | #[repr(u64)] 20 | #[derive(Clone, Copy, Debug, PartialEq)] 21 | pub enum NSAlertStyle { 22 | warning = 0, // Same visual as informational 23 | informational = 1, 24 | critical = 2, 25 | } 26 | 27 | #[allow(non_upper_case_globals)] 28 | pub static NSModalPannelWindowLevel: i32 = 10; 29 | 30 | /** 31 | * NSAlert 32 | * https://developer.apple.com/documentation/appkit/nsalert 33 | */ 34 | pub trait NSAlert: Sized { 35 | unsafe fn alloc(_: Self) -> id { 36 | msg_send![class!(NSAlert), alloc] 37 | } 38 | 39 | unsafe fn init(self) -> id; 40 | unsafe fn autorelease(self) -> id; 41 | 42 | unsafe fn setAlertStyle(self, style: NSAlertStyle); 43 | unsafe fn setMessageText(self, messageText: id); 44 | unsafe fn setInformativeText(self, informativeText: id); 45 | unsafe fn addButton(self, withTitle: id); 46 | unsafe fn window(self) -> id; 47 | unsafe fn setWindowLevel(self, level: i32); 48 | unsafe fn runModal(self) -> id; 49 | } 50 | 51 | impl NSAlert for id { 52 | unsafe fn init(self) -> id { 53 | msg_send![self, init] 54 | } 55 | 56 | unsafe fn autorelease(self) -> id { 57 | msg_send![self, autorelease] 58 | } 59 | 60 | unsafe fn setAlertStyle(self, alertStyle: NSAlertStyle) { 61 | msg_send![self, setAlertStyle: alertStyle] 62 | } 63 | 64 | unsafe fn setMessageText(self, messageText: id) { 65 | msg_send![self, setMessageText: messageText] 66 | } 67 | 68 | unsafe fn setInformativeText(self, informativeText: id) { 69 | msg_send![self, setInformativeText: informativeText] 70 | } 71 | 72 | unsafe fn addButton(self, withTitle: id) { 73 | msg_send![self, addButtonWithTitle: withTitle] 74 | } 75 | 76 | unsafe fn window(self) -> id { 77 | msg_send![self, window] 78 | } 79 | 80 | unsafe fn runModal(self) -> id { 81 | msg_send![self, runModal] 82 | } 83 | 84 | unsafe fn setWindowLevel(self, level: i32) { 85 | msg_send![self.window(), setLevel: level] 86 | } 87 | } 88 | 89 | pub fn create( 90 | title: &str, 91 | content: &str, 92 | icon_type: IconType, 93 | ) -> std::result::Result<(), MsgBoxError> { 94 | let alert_style = match icon_type { 95 | IconType::Error => NSAlertStyle::critical, 96 | IconType::Info => NSAlertStyle::informational, 97 | 98 | // AppKit doesn't support NSAlert without any icon 99 | IconType::None => NSAlertStyle::informational, 100 | }; 101 | 102 | unsafe { 103 | let alert = NSAlert::alloc(nil).init().autorelease(); 104 | alert.addButton(NSString::alloc(nil).init_str("OK")); 105 | alert.setMessageText(NSString::alloc(nil).init_str(title)); 106 | alert.setInformativeText(NSString::alloc(nil).init_str(content)); 107 | alert.setAlertStyle(alert_style); 108 | // Force the alert to appear on top of any other windows 109 | alert.setWindowLevel(NSModalPannelWindowLevel); 110 | alert.runModal(); 111 | 112 | // TODO: Find a away to detect that NSAlert.runModal() failed? 113 | Ok(()) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | use std::iter::once; 2 | use std::ptr::null_mut; 3 | use winapi::um::winuser::{MessageBoxW, MB_ICONERROR, MB_ICONINFORMATION, MB_OK, MB_SYSTEMMODAL}; 4 | 5 | use common::{IconType, MsgBoxError}; 6 | 7 | pub fn create( 8 | title: &str, 9 | content: &str, 10 | icon_type: IconType, 11 | ) -> std::result::Result<(), MsgBoxError> { 12 | let lp_text: Vec = content.encode_utf16().chain(once(0)).collect(); 13 | let lp_caption: Vec = title.encode_utf16().chain(once(0)).collect(); 14 | 15 | let window_type = match icon_type { 16 | IconType::Error => MB_OK | MB_ICONERROR | MB_SYSTEMMODAL, 17 | IconType::Info => MB_OK | MB_ICONINFORMATION | MB_SYSTEMMODAL, 18 | IconType::None => MB_OK | MB_SYSTEMMODAL, 19 | }; 20 | 21 | unsafe { 22 | /* 23 | * https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw#return-value 24 | * If the return value is zero, creating message box has failed 25 | */ 26 | match MessageBoxW( 27 | null_mut(), 28 | lp_text.as_ptr(), 29 | lp_caption.as_ptr(), 30 | window_type, 31 | ) { 32 | 0 => Err(MsgBoxError::Create(())), 33 | _ => Ok(()), 34 | } 35 | } 36 | } 37 | --------------------------------------------------------------------------------