feat: hide dock icon (#35)

This commit is contained in:
lencx
2022-12-16 11:43:29 +08:00
parent 7da70733a3
commit 647a89fdf8
10 changed files with 182 additions and 93 deletions

View File

@@ -60,11 +60,13 @@ cask "popcorn-time", args: { "no-quarantine": true }
- **Preferences (喜好)** - **Preferences (喜好)**
- `Theme` - `Light`, `Dark` (仅支持 macOS 和 Windows) - `Theme` - `Light`, `Dark` (仅支持 macOS 和 Windows)
- `Always On Top`: 窗口置顶 - `Stay On Top`: 窗口置顶
- `Titlebar`: 是否显示 `Titlebar`,仅 macOS 支持 - `Titlebar`: 是否显示 `Titlebar`,仅 macOS 支持
- `Inject Script`: 用于修改网站的用户自定义脚本 - `Inject Script`: 用于修改网站的用户自定义脚本
- `Hide Dock Icon` ([#35](https://github.com/lencx/ChatGPT/issues/35)): 隐藏 Dock 中的应用图标 (仅 macOS 支持)
- 右键单击系统托盘图标来显示或隐藏在 Dock 里的应用图标
- `Control Center`: ChatGPT 应用的控制中心,它将为应用提供无限的可能 - `Control Center`: ChatGPT 应用的控制中心,它将为应用提供无限的可能
- 设置 `Theme``Always on Top``Titlebar` - 设置 `Theme``Stay On Top``Titlebar`
- `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): 自定义 `user agent` 防止网站安全检测,默认值为空 - `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): 自定义 `user agent` 防止网站安全检测,默认值为空
- `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): 切换网站源地址,默认为 `https://chat.openai.com`。需要注意的是镜像网站的 UI 需要和原网站一致,否则可能会导致某些功能不工作 - `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): 切换网站源地址,默认为 `https://chat.openai.com`。需要注意的是镜像网站的 UI 需要和原网站一致,否则可能会导致某些功能不工作
- `Go to Config`: 打开 ChatGPT 配置目录 (`path: ~/.chatgpt/*`) - `Go to Config`: 打开 ChatGPT 配置目录 (`path: ~/.chatgpt/*`)

View File

@@ -61,11 +61,13 @@ cask "popcorn-time", args: { "no-quarantine": true }
- **Preferences** - **Preferences**
- `Theme` - `Light`, `Dark` (Only macOS and Windows are supported). - `Theme` - `Light`, `Dark` (Only macOS and Windows are supported).
- `Always on Top`: The window is always on top of other windows. - `Stay On Top`: The window is stay on top of other windows.
- `Titlebar`: Whether to display the titlebar, supported by macOS only. - `Titlebar`: Whether to display the titlebar, supported by macOS only.
- `Hide Dock Icon` ([#35](https://github.com/lencx/ChatGPT/issues/35)): Hide application icons from the Dock(support macOS only).
- Right-click on the system tray icon to show or hide the application icons in the Dock
- `Inject Script`: Using scripts to modify pages. - `Inject Script`: Using scripts to modify pages.
- `Control Center`: The control center of ChatGPT application, it will give unlimited imagination to the application. - `Control Center`: The control center of ChatGPT application, it will give unlimited imagination to the application.
- `Theme`, `Always on Top`, `Titlebar`, ... - `Theme`, `Stay On Top`, `Titlebar`, ...
- `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): Custom `user agent`, which may be required in some scenarios. The default value is the empty string. - `User Agent` ([#17](https://github.com/lencx/ChatGPT/issues/17)): Custom `user agent`, which may be required in some scenarios. The default value is the empty string.
- `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): Switch the site source address, the default is `https://chat.openai.com`, please make sure the mirror site UI is the same as the original address. Otherwise, some functions may not be available. - `Switch Origin` ([#14](https://github.com/lencx/ChatGPT/issues/14)): Switch the site source address, the default is `https://chat.openai.com`, please make sure the mirror site UI is the same as the original address. Otherwise, some functions may not be available.
- `Go to Config`: Open the configuration file directory (`path: ~/.chatgpt/*`). - `Go to Config`: Open the configuration file directory (`path: ~/.chatgpt/*`).

View File

@@ -1,5 +1,10 @@
# UPDATE LOG # UPDATE LOG
## v0.4.0
feat: menu enhancement
- hide application icons from the Dock (support macOS only)
## v0.3.0 ## v0.3.0
fix: can't open ChatGPT fix: can't open ChatGPT
@@ -36,7 +41,7 @@ feat: tray window
## v0.1.6 ## v0.1.6
feat: feat:
- always on top - stay on top
- export ChatGPT history - export ChatGPT history
## v0.1.5 ## v0.1.5

View File

@@ -3,15 +3,15 @@ use crate::{
utils, utils,
}; };
use tauri::{ use tauri::{
utils::assets::EmbeddedAssets, AboutMetadata, AppHandle, Context, CustomMenuItem, Manager, AboutMetadata, AppHandle, CustomMenuItem, Manager, Menu, MenuItem, Submenu, SystemTray,
Menu, MenuItem, Submenu, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowMenuEvent, SystemTrayEvent, SystemTrayMenu, WindowMenuEvent,
}; };
use tauri_plugin_positioner::{on_tray_event, Position, WindowExt}; use tauri_plugin_positioner::{on_tray_event, Position, WindowExt};
// --- Menu // --- Menu
pub fn init(context: &Context<EmbeddedAssets>) -> Menu { pub fn init() -> Menu {
let chat_conf = ChatConfJson::get_chat_conf(); let chat_conf = ChatConfJson::get_chat_conf();
let name = &context.package_info().name; let name = "ChatGPT";
let app_menu = Submenu::new( let app_menu = Submenu::new(
name, name,
Menu::new() Menu::new()
@@ -25,18 +25,18 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
.add_native_item(MenuItem::Quit), .add_native_item(MenuItem::Quit),
); );
let always_on_top = CustomMenuItem::new("always_on_top".to_string(), "Always on Top") let stay_on_top =
.accelerator("CmdOrCtrl+T"); CustomMenuItem::new("stay_on_top".to_string(), "Stay On Top").accelerator("CmdOrCtrl+T");
let titlebar = let titlebar =
CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B"); CustomMenuItem::new("titlebar".to_string(), "Titlebar").accelerator("CmdOrCtrl+B");
let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light"); let theme_light = CustomMenuItem::new("theme_light".to_string(), "Light");
let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark"); let theme_dark = CustomMenuItem::new("theme_dark".to_string(), "Dark");
let is_dark = chat_conf.theme == "Dark"; let is_dark = chat_conf.theme == "Dark";
let always_on_top_menu = if chat_conf.always_on_top { let stay_on_top_menu = if chat_conf.stay_on_top {
always_on_top.selected() stay_on_top.selected()
} else { } else {
always_on_top stay_on_top
}; };
let titlebar_menu = if chat_conf.titlebar { let titlebar_menu = if chat_conf.titlebar {
titlebar.selected() titlebar.selected()
@@ -62,9 +62,11 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
}), }),
) )
.into(), .into(),
always_on_top_menu.into(), stay_on_top_menu.into(),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
titlebar_menu.into(), titlebar_menu.into(),
#[cfg(target_os = "macos")]
CustomMenuItem::new("hide_dock_icon".to_string(), "Hide Dock Icon").into(),
MenuItem::Separator.into(), MenuItem::Separator.into(),
CustomMenuItem::new("inject_script".to_string(), "Inject Script") CustomMenuItem::new("inject_script".to_string(), "Inject Script")
.accelerator("CmdOrCtrl+J") .accelerator("CmdOrCtrl+J")
@@ -119,7 +121,6 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen") CustomMenuItem::new("scroll_bottom".to_string(), "Scroll to Bottom of Screen")
.accelerator("CmdOrCtrl+Down"), .accelerator("CmdOrCtrl+Down"),
) )
.add_native_item(MenuItem::Zoom)
.add_native_item(MenuItem::Separator) .add_native_item(MenuItem::Separator)
.add_item( .add_item(
CustomMenuItem::new("reload".to_string(), "Refresh the Screen") CustomMenuItem::new("reload".to_string(), "Refresh the Screen")
@@ -127,6 +128,13 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
), ),
); );
let window_menu = Submenu::new(
"Window",
Menu::new()
.add_native_item(MenuItem::Minimize)
.add_native_item(MenuItem::Zoom),
);
let help_menu = Submenu::new( let help_menu = Submenu::new(
"Help", "Help",
Menu::new() Menu::new()
@@ -143,6 +151,7 @@ pub fn init(context: &Context<EmbeddedAssets>) -> Menu {
.add_submenu(preferences_menu) .add_submenu(preferences_menu)
.add_submenu(edit_menu) .add_submenu(edit_menu)
.add_submenu(view_menu) .add_submenu(view_menu)
.add_submenu(window_menu)
.add_submenu(help_menu) .add_submenu(help_menu)
} }
@@ -165,6 +174,9 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
"go_conf" => utils::open_file(utils::chat_root()), "go_conf" => utils::open_file(utils::chat_root()),
"clear_conf" => utils::clear_conf(&app), "clear_conf" => utils::clear_conf(&app),
"awesome" => open(&app, conf::AWESOME_URL.to_string()), "awesome" => open(&app, conf::AWESOME_URL.to_string()),
"hide_dock_icon" => {
ChatConfJson::amend(&serde_json::json!({ "hide_dock_icon": true }), Some(app)).unwrap()
}
"titlebar" => { "titlebar" => {
let chat_conf = conf::ChatConfJson::get_chat_conf(); let chat_conf = conf::ChatConfJson::get_chat_conf();
ChatConfJson::amend( ChatConfJson::amend(
@@ -182,19 +194,15 @@ pub fn menu_handler(event: WindowMenuEvent<tauri::Wry>) {
}; };
ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap(); ChatConfJson::amend(&serde_json::json!({ "theme": theme }), Some(app)).unwrap();
} }
"always_on_top" => { "stay_on_top" => {
let mut always_on_top = state.always_on_top.lock().unwrap(); let mut stay_on_top = state.stay_on_top.lock().unwrap();
*always_on_top = !*always_on_top; *stay_on_top = !*stay_on_top;
menu_handle menu_handle
.get_item(menu_id) .get_item(menu_id)
.set_selected(*always_on_top) .set_selected(*stay_on_top)
.unwrap(); .unwrap();
win.set_always_on_top(*always_on_top).unwrap(); win.set_always_on_top(*stay_on_top).unwrap();
ChatConfJson::amend( ChatConfJson::amend(&serde_json::json!({ "stay_on_top": *stay_on_top }), None).unwrap();
&serde_json::json!({ "always_on_top": *always_on_top }),
None,
)
.unwrap();
} }
// View // View
"reload" => win.eval("window.location.reload()").unwrap(), "reload" => win.eval("window.location.reload()").unwrap(),
@@ -235,20 +243,70 @@ pub fn tray_menu() -> SystemTray {
// --- SystemTray Event // --- SystemTray Event
pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) { pub fn tray_handler(handle: &AppHandle, event: SystemTrayEvent) {
let core_win = handle.get_window("core").unwrap();
on_tray_event(handle, &event); on_tray_event(handle, &event);
if let SystemTrayEvent::LeftClick { .. } = event { match event {
core_win.minimize().unwrap(); SystemTrayEvent::LeftClick { .. } => {
let mini_win = handle.get_window("mini").unwrap(); let chat_conf = conf::ChatConfJson::get_chat_conf();
mini_win.move_window(Position::TrayCenter).unwrap();
if mini_win.is_visible().unwrap() { if !chat_conf.hide_dock_icon {
mini_win.hide().unwrap(); let core_win = handle.get_window("core").unwrap();
} else { core_win.minimize().unwrap();
mini_win.show().unwrap(); }
let tray_win = handle.get_window("tray").unwrap();
tray_win.move_window(Position::TrayCenter).unwrap();
if tray_win.is_visible().unwrap() {
tray_win.hide().unwrap();
} else {
tray_win.show().unwrap();
}
} }
SystemTrayEvent::RightClick { .. } => {
let chat_conf = conf::ChatConfJson::get_chat_conf();
let hide_dock = !chat_conf.hide_dock_icon;
let title;
let msg;
if !hide_dock {
title = "Show Dock Icon";
msg = "Are you sure you want to show the ChatGPT icon in the Dock?";
} else {
title = "Hide Dock Icon";
msg = "Are you sure you want to hide the ChatGPT icon in the Dock?";
}
let app = handle.clone();
tauri::api::dialog::ask(
handle.get_window("tray").as_ref(),
title,
msg,
move |is_ok| {
if is_ok {
ChatConfJson::amend(
&serde_json::json!({ "hide_dock_icon": hide_dock }),
Some(app),
)
.unwrap();
}
},
);
}
_ => (),
} }
// if let SystemTrayEvent::LeftClick { .. } = event {
// core_win.minimize().unwrap();
// let tray_win = handle.get_window("tray").unwrap();
// tray_win.move_window(Position::TrayCenter).unwrap();
// if tray_win.is_visible().unwrap() {
// tray_win.hide().unwrap();
// } else {
// tray_win.show().unwrap();
// }
// }
} }
pub fn open(app: &AppHandle, path: String) { pub fn open(app: &AppHandle, path: String) {

View File

@@ -5,40 +5,52 @@ pub fn init(app: &mut App) -> std::result::Result<(), Box<dyn std::error::Error>
let chat_conf = ChatConfJson::get_chat_conf(); let chat_conf = ChatConfJson::get_chat_conf();
let url = chat_conf.origin.to_string(); let url = chat_conf.origin.to_string();
let theme = ChatConfJson::theme(); let theme = ChatConfJson::theme();
window::mini_window(&app.app_handle()); let handle = app.app_handle();
#[cfg(target_os = "macos")] std::thread::spawn(move || {
WindowBuilder::new(app, "core", WindowUrl::App(url.into())) window::tray_window(&handle);
.resizable(true) });
.fullscreen(false)
.inner_size(800.0, 600.0)
.hidden_title(true)
.theme(theme)
.always_on_top(chat_conf.always_on_top)
.title_bar_style(ChatConfJson::titlebar())
.initialization_script(&utils::user_script())
.initialization_script(include_str!("../assets/html2canvas.js"))
.initialization_script(include_str!("../assets/jspdf.js"))
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/export.js"))
.user_agent(&chat_conf.ua_window)
.build()?;
#[cfg(not(target_os = "macos"))] if chat_conf.hide_dock_icon {
WindowBuilder::new(app, "core", WindowUrl::App(url.into())) #[cfg(target_os = "macos")]
.title("ChatGPT") app.set_activation_policy(tauri::ActivationPolicy::Accessory);
.resizable(true) } else {
.fullscreen(false) let app = app.handle();
.inner_size(800.0, 600.0) std::thread::spawn(move || {
.theme(theme) #[cfg(target_os = "macos")]
.always_on_top(chat_conf.always_on_top) WindowBuilder::new(&app, "core", WindowUrl::App(url.into()))
.initialization_script(&utils::user_script()) .resizable(true)
.initialization_script(include_str!("../assets/html2canvas.js")) .fullscreen(false)
.initialization_script(include_str!("../assets/jspdf.js")) .inner_size(800.0, 600.0)
.initialization_script(include_str!("../assets/core.js")) .hidden_title(true)
.initialization_script(include_str!("../assets/export.js")) .theme(theme)
.user_agent(&chat_conf.ua_window) .always_on_top(chat_conf.stay_on_top)
.build()?; .title_bar_style(ChatConfJson::titlebar())
.initialization_script(&utils::user_script())
.initialization_script(include_str!("../assets/html2canvas.js"))
.initialization_script(include_str!("../assets/jspdf.js"))
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/export.js"))
.user_agent(&chat_conf.ua_window)
.build().unwrap();
#[cfg(not(target_os = "macos"))]
WindowBuilder::new(&app, "core", WindowUrl::App(url.into()))
.title("ChatGPT")
.resizable(true)
.fullscreen(false)
.inner_size(800.0, 600.0)
.theme(theme)
.always_on_top(chat_conf.stay_on_top)
.initialization_script(&utils::user_script())
.initialization_script(include_str!("../assets/html2canvas.js"))
.initialization_script(include_str!("../assets/jspdf.js"))
.initialization_script(include_str!("../assets/core.js"))
.initialization_script(include_str!("../assets/export.js"))
.user_agent(&chat_conf.ua_window)
.build().unwrap();
});
}
Ok(()) Ok(())
} }

View File

@@ -1,25 +1,28 @@
use crate::{conf, utils}; use crate::{conf, utils};
use tauri::{utils::config::WindowUrl, window::WindowBuilder}; use tauri::{utils::config::WindowUrl, window::WindowBuilder};
pub fn mini_window(handle: &tauri::AppHandle) { pub fn tray_window(handle: &tauri::AppHandle) {
let chat_conf = conf::ChatConfJson::get_chat_conf(); let chat_conf = conf::ChatConfJson::get_chat_conf();
let theme = conf::ChatConfJson::theme(); let theme = conf::ChatConfJson::theme();
let app = handle.clone();
WindowBuilder::new(handle, "mini", WindowUrl::App(chat_conf.origin.into())) std::thread::spawn(move || {
.resizable(false) WindowBuilder::new(&app, "tray", WindowUrl::App(chat_conf.origin.into()))
.fullscreen(false) .resizable(false)
.inner_size(360.0, 540.0) .fullscreen(false)
.decorations(false) .inner_size(360.0, 540.0)
.always_on_top(true) .decorations(false)
.theme(theme) .always_on_top(true)
.initialization_script(&utils::user_script()) .theme(theme)
.initialization_script(include_str!("../assets/html2canvas.js")) .initialization_script(&utils::user_script())
.initialization_script(include_str!("../assets/jspdf.js")) .initialization_script(include_str!("../assets/html2canvas.js"))
.initialization_script(include_str!("../assets/core.js")) .initialization_script(include_str!("../assets/jspdf.js"))
.initialization_script(include_str!("../assets/export.js")) .initialization_script(include_str!("../assets/core.js"))
.user_agent(&chat_conf.ua_tray) .initialization_script(include_str!("../assets/export.js"))
.build() .user_agent(&chat_conf.ua_tray)
.unwrap() .build()
.hide() .unwrap()
.unwrap(); .hide()
.unwrap();
});
} }

View File

@@ -41,7 +41,7 @@ window.invoke = invoke;
window.transformCallback = transformCallback; window.transformCallback = transformCallback;
async function init() { async function init() {
if (__TAURI_METADATA__.__currentWindow.label === 'mini') { if (__TAURI_METADATA__.__currentWindow.label === 'tray') {
document.getElementsByTagName('html')[0].style['font-size'] = '70%'; document.getElementsByTagName('html')[0].style['font-size'] = '70%';
} }

View File

@@ -14,18 +14,20 @@ pub const ISSUES_URL: &str = "https://github.com/lencx/ChatGPT/issues";
pub const UPDATE_LOG_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md"; pub const UPDATE_LOG_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/UPDATE_LOG.md";
pub const AWESOME_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/AWESOME.md"; pub const AWESOME_URL: &str = "https://github.com/lencx/ChatGPT/blob/main/AWESOME.md";
pub const DEFAULT_CHAT_CONF: &str = r#"{ pub const DEFAULT_CHAT_CONF: &str = r#"{
"always_on_top": false, "stay_on_top": false,
"theme": "Light", "theme": "Light",
"titlebar": true, "titlebar": true,
"hide_dock_icon": false,
"default_origin": "https://chat.openai.com", "default_origin": "https://chat.openai.com",
"origin": "https://chat.openai.com", "origin": "https://chat.openai.com",
"ua_window": "", "ua_window": "",
"ua_tray": "" "ua_tray": ""
}"#; }"#;
pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{ pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{
"always_on_top": false, "stay_on_top": false,
"theme": "Light", "theme": "Light",
"titlebar": false, "titlebar": false,
"hide_dock_icon": false,
"default_origin": "https://chat.openai.com", "default_origin": "https://chat.openai.com",
"origin": "https://chat.openai.com", "origin": "https://chat.openai.com",
"ua_window": "", "ua_window": "",
@@ -33,22 +35,27 @@ pub const DEFAULT_CHAT_CONF_MAC: &str = r#"{
}"#; }"#;
pub struct ChatState { pub struct ChatState {
pub always_on_top: Mutex<bool>, pub stay_on_top: Mutex<bool>,
} }
impl ChatState { impl ChatState {
pub fn default(chat_conf: ChatConfJson) -> Self { pub fn default(chat_conf: ChatConfJson) -> Self {
ChatState { ChatState {
always_on_top: Mutex::new(chat_conf.always_on_top), stay_on_top: Mutex::new(chat_conf.stay_on_top),
} }
} }
} }
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
pub struct ChatConfJson { pub struct ChatConfJson {
// support macOS only
pub titlebar: bool, pub titlebar: bool,
pub always_on_top: bool, pub hide_dock_icon: bool,
// macOS and Windows
pub theme: String, pub theme: String,
pub stay_on_top: bool,
pub default_origin: String, pub default_origin: String,
pub origin: String, pub origin: String,
pub ua_window: String, pub ua_window: String,

View File

@@ -29,7 +29,7 @@ fn main() {
]) ])
.setup(setup::init) .setup(setup::init)
.plugin(tauri_plugin_positioner::init()) .plugin(tauri_plugin_positioner::init())
.menu(menu::init(&context)) .menu(menu::init())
.system_tray(menu::tray_menu()) .system_tray(menu::tray_menu())
.on_menu_event(menu::menu_handler) .on_menu_event(menu::menu_handler)
.on_system_tray_event(menu::tray_handler) .on_system_tray_event(menu::tray_handler)

View File

@@ -72,7 +72,7 @@ export default function General() {
<Radio value="Dark">Dark</Radio> <Radio value="Dark">Dark</Radio>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item label="Always on Top" name="always_on_top" valuePropName="checked"> <Form.Item label="Stay On Top" name="stay_on_top" valuePropName="checked">
<Switch /> <Switch />
</Form.Item> </Form.Item>
{platformInfo === 'darwin' && ( {platformInfo === 'darwin' && (