-
-
-
-
-
Qopy
-
-
-
-
Paste
-
-
-
-
-
-
-
-
- {{ group.label }}
-
-
-
-
-
-
-
-
-
-
Image ({{ item.dimensions || 'Loading...' }})
-
{{ truncateContent(item.content) }}
-
-
-
-
-
-
-
-
- {{ selectedItem?.content || '' }}
-
-
+
+
\ No newline at end of file
diff --git a/assets/css/style.scss b/assets/css/index.scss
similarity index 84%
rename from assets/css/style.scss
rename to assets/css/index.scss
index 28f241b..3390c53 100644
--- a/assets/css/style.scss
+++ b/assets/css/index.scss
@@ -6,56 +6,6 @@ $text: #E5DFD5;
$text2: #ADA9A1;
$mutedtext: #78756F;
-@font-face {
- font-family: SFRoundedRegular;
- font-display: swap;
- src: url("~/assets/fonts/SFRoundedRegular.otf") format("woff2");
-}
-
-@font-face {
- font-family: SFRoundedMedium;
- font-display: swap;
- src: url("~/assets/fonts/SFRoundedMedium.otf") format("woff2");
-}
-
-@font-face {
- font-family: SFRoundedSemiBold;
- font-display: swap;
- src: url("~/assets/fonts/SFRoundedSemiBold.otf") format("woff2");
-}
-
-@font-face {
- font-family: SFMonoRegular;
- font-display: swap;
- src: url("~/assets/fonts/SFMonoRegular.otf") format("woff2");
-}
-
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- color: $text;
- text-decoration: none;
- font-family: SFRoundedRegular;
- scroll-behavior: smooth;
- scrollbar-width: thin;
- user-select: none;
-
- --os-handle-bg: #ADA9A1;
- --os-handle-bg-hover: #78756F;
- --os-handle-bg-active: #78756F;
-}
-
-html,
-body,
-#__nuxt {
- background-color: transparent;
-}
-
-.os-scrollbar-horizontal {
- display: none;
-}
-
.bg {
width: 750px;
height: 474px;
@@ -88,7 +38,7 @@ body,
width: 284px;
top: 53px;
left: 0;
- height: calc(100vh - 96px);
+ height: calc(100vh - 95px);
border-right: 1px solid $divider;
display: flex;
flex-direction: column;
@@ -105,7 +55,7 @@ body,
padding: 10px;
padding-inline: 10px;
letter-spacing: 0.5px;
- gap: 16px;
+ gap: 10px;
overflow: hidden;
text-overflow: clip;
white-space: nowrap;
@@ -129,12 +79,13 @@ body,
}
.favicon {
- width: 20px;
+ width: 18px;
+ height: 18px;
}
.image {
- width: 20px;
- height: 20px;
+ width: 18px;
+ height: 18px;
}
.icon {
@@ -149,7 +100,7 @@ body,
left: 284px;
padding: 8px;
height: calc(100vh - 96px);
- font-family: SFMonoRegular !important;
+ font-family: CommitMono !important;
font-size: 14px;
letter-spacing: 1;
border-radius: 10px;
@@ -159,7 +110,7 @@ body,
div {
border-radius: 10px;
- font-family: SFMonoRegular !important;
+ font-family: CommitMono !important;
}
.full-image {
@@ -178,12 +129,12 @@ body,
}
.bottom-bar {
- height: 41px;
- width: calc(100vw - 3px);
+ height: 40px;
+ width: calc(100vw - 2px);
backdrop-filter: blur(18px);
- background-color: rgba(46, 45, 43, 0.8);
+ background-color: hsla(40, 3%, 16%, 0.8);
position: fixed;
- bottom: 2px;
+ bottom: 1px;
left: 1px;
z-index: 100;
border-radius: 0 0 12px 12px;
@@ -191,6 +142,8 @@ body,
flex-direction: row;
justify-content: space-between;
padding-inline: 12px;
+ padding-right: 6px;
+ padding-top: 1px;
align-items: center;
font-size: 14px;
border-top: 1px solid $divider;
@@ -240,9 +193,10 @@ body,
display: flex;
align-items: center;
gap: 8px;
- border-radius: 6px;
+ border-radius: 7px;
background-color: transparent;
transition: all .2s;
+ cursor: pointer;
}
.paste:hover,
diff --git a/assets/css/keybind.scss b/assets/css/keybind.scss
new file mode 100644
index 0000000..e1b871a
--- /dev/null
+++ b/assets/css/keybind.scss
@@ -0,0 +1,66 @@
+$primary: #2E2D2B;
+$accent: #FEB453;
+$divider: #ffffff0d;
+
+$text: #E5DFD5;
+$text2: #ADA9A1;
+$mutedtext: #78756F;
+
+.bg {
+ width: 750px;
+ height: 474px;
+ background-color: $primary;
+ border: 1px solid $divider;
+ border-radius: 12px;
+ z-index: -1;
+ position: fixed;
+ outline: none;
+}
+
+.keybind-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ padding: 20px;
+}
+
+h2 {
+ margin-bottom: 20px;
+}
+
+.keybind-input {
+ width: 300px;
+ height: 50px;
+ border: 2px solid $accent;
+ border-radius: 5px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ cursor: pointer;
+ margin-bottom: 20px;
+ background-color: rgba($accent, 0.1);
+ user-select: none;
+}
+
+.keybind-input:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px rgba($accent, 0.5);
+}
+
+button {
+ padding: 10px 20px;
+ background-color: $accent;
+ color: $primary;
+ border: none;
+ border-radius: 5px;
+ font-size: 16px;
+ cursor: pointer;
+}
+
+button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
diff --git a/assets/fonts/CommitMono.woff2 b/assets/fonts/CommitMono.woff2
new file mode 100644
index 0000000..f9ac1ee
Binary files /dev/null and b/assets/fonts/CommitMono.woff2 differ
diff --git a/assets/fonts/SFMonoRegular.otf b/assets/fonts/SFMonoRegular.otf
deleted file mode 100644
index 4c6f9f6..0000000
Binary files a/assets/fonts/SFMonoRegular.otf and /dev/null differ
diff --git a/components/Icons/Code.vue b/components/Icons/Code.vue
index a0a7503..b521a9f 100644
--- a/components/Icons/Code.vue
+++ b/components/Icons/Code.vue
@@ -1,3 +1,11 @@
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Icons/File.vue b/components/Icons/File.vue
index f65c400..97136c0 100644
--- a/components/Icons/File.vue
+++ b/components/Icons/File.vue
@@ -1,3 +1,11 @@
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Icons/Image.vue b/components/Icons/Image.vue
index 5903bff..42eca9f 100644
--- a/components/Icons/Image.vue
+++ b/components/Icons/Image.vue
@@ -1,3 +1,11 @@
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/Icons/Text.vue b/components/Icons/Text.vue
index 54aee4b..a35d18e 100644
--- a/components/Icons/Text.vue
+++ b/components/Icons/Text.vue
@@ -1,3 +1,11 @@
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/index.vue b/pages/index.vue
new file mode 100644
index 0000000..e045e51
--- /dev/null
+++ b/pages/index.vue
@@ -0,0 +1,459 @@
+
+
+
+
+
+
+
Qopy
+
+
+
+
Paste
+
+
+
+
+
+
+
+
+ {{ group.label }}
+
+
+
+
+
+
+
+
+
+
Image ({{ item.dimensions || 'Loading...' }})
+
{{ truncateContent(item.content) }}
+
+
+
+
+
+
+
+
+ {{ selectedItem?.content || '' }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pages/keybind.vue b/pages/keybind.vue
new file mode 100644
index 0000000..d9fef25
--- /dev/null
+++ b/pages/keybind.vue
@@ -0,0 +1,53 @@
+
+
+
+
Set New Keybind
+
+ {{ currentKeybind || 'Click here, then press your desired key combination' }}
+
+
Save Keybind
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/Code.svg b/public/Code.svg
deleted file mode 100644
index af5a433..0000000
--- a/public/Code.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/Image.svg b/public/Image.svg
deleted file mode 100644
index 2a7df95..0000000
--- a/public/Image.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/Text.svg b/public/Text.svg
deleted file mode 100644
index 2c3d636..0000000
--- a/public/Text.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/file.svg b/public/file.svg
deleted file mode 100644
index d544d17..0000000
--- a/public/file.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
index 10e711b..883a577 100644
--- a/src-tauri/capabilities/default.json
+++ b/src-tauri/capabilities/default.json
@@ -28,6 +28,7 @@
"core:window:allow-show",
"core:window:allow-set-focus",
"core:window:allow-is-focused",
- "core:window:allow-is-visible"
+ "core:window:allow-is-visible",
+ "fs:allow-read"
]
}
\ No newline at end of file
diff --git a/src-tauri/src/api/clipboard.rs b/src-tauri/src/api/clipboard.rs
index e382aa0..c587cb4 100644
--- a/src-tauri/src/api/clipboard.rs
+++ b/src-tauri/src/api/clipboard.rs
@@ -24,11 +24,12 @@ pub fn set_app_data_dir(path: std::path::PathBuf) {
}
#[tauri::command]
-pub fn read_image(filename: String) -> Result
, String> {
+pub fn read_image(filename: String) -> Result {
let app_data_dir = APP_DATA_DIR.lock().unwrap();
let app_data_dir = app_data_dir.as_ref().expect("App data directory not set");
let image_path = app_data_dir.join("images").join(filename);
- fs::read(image_path).map_err(|e| e.to_string())
+ let image_data = fs::read(image_path).map_err(|e| e.to_string())?;
+ Ok(STANDARD.encode(image_data))
}
#[tauri::command]
@@ -107,26 +108,22 @@ pub fn setup(app: &AppHandle) {
let app = app.clone();
runtime.block_on(async move {
if IS_PROGRAMMATIC_PASTE.load(Ordering::SeqCst) {
- println!("Ignoring programmatic paste");
return;
}
let clipboard = app.state::();
let available_types = clipboard.available_types().unwrap();
- println!("Clipboard update detected");
-
match get_pool(&app).await {
Ok(pool) => {
if available_types.image {
println!("Handling image change");
if let Ok(image_data) = clipboard.read_image_base64() {
- let base64_image = STANDARD.encode(&image_data);
insert_content_if_not_exists(
app.clone(),
pool.clone(),
"image",
- base64_image,
+ image_data,
)
.await;
}
diff --git a/src-tauri/src/api/hotkeys.rs b/src-tauri/src/api/hotkeys.rs
index eaa23e6..ca93955 100644
--- a/src-tauri/src/api/hotkeys.rs
+++ b/src-tauri/src/api/hotkeys.rs
@@ -1,31 +1,136 @@
-use rdev::{listen, EventType, Key};
-use tauri::Manager;
+use rdev::{listen, Event, EventType, Key};
+use tauri::{Manager, Emitter};
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Mutex};
+use std::collections::HashSet;
+use serde::Serialize;
use crate::utils::commands::center_window_on_current_monitor;
+static IS_CAPTURING_KEYBIND: AtomicBool = AtomicBool::new(false);
+
+#[derive(Debug, Clone, Serialize)]
+struct CapturedKeybind {
+ modifiers: Vec,
+ key: String,
+}
+
+struct KeybindState {
+ pressed_keys: HashSet,
+}
+
+impl KeybindState {
+ fn new() -> Self {
+ Self {
+ pressed_keys: HashSet::new(),
+ }
+ }
+}
+
pub fn setup(app_handle: tauri::AppHandle) {
+ let app_handle_clone = app_handle.clone();
+ let keybind_state = Arc::new(Mutex::new(KeybindState::new()));
+
std::thread::spawn(move || {
- let mut meta_pressed = false;
- listen(move |event| {
- match event.event_type {
- EventType::KeyPress(Key::MetaLeft) | EventType::KeyPress(Key::MetaRight) => {
- meta_pressed = true;
- }
- EventType::KeyRelease(Key::MetaLeft) | EventType::KeyRelease(Key::MetaRight) => {
- meta_pressed = false;
- }
- EventType::KeyPress(Key::KeyV) => {
- if meta_pressed {
- meta_pressed = false;
- let window = app_handle.get_webview_window("main").unwrap();
- window.show().unwrap();
- window.set_focus().unwrap();
- center_window_on_current_monitor(&window);
- }
- }
- _ => {}
+ if let Err(e) = listen(move |event| {
+ let mut state = keybind_state.lock().unwrap();
+ if IS_CAPTURING_KEYBIND.load(Ordering::SeqCst) {
+ handle_keybind_capture(&app_handle_clone, event, &mut state);
+ } else {
+ handle_normal_hotkey(&app_handle_clone, event, &mut state);
}
- })
- .unwrap();
+ }) {
+ eprintln!("Error setting up event listener: {:?}", e);
+ }
});
+}
+
+fn handle_normal_hotkey(app_handle: &tauri::AppHandle, event: Event, state: &mut KeybindState) {
+ match event.event_type {
+ EventType::KeyPress(Key::MetaLeft) | EventType::KeyPress(Key::MetaRight) => {
+ state.pressed_keys.insert(Key::MetaLeft);
+ }
+ EventType::KeyRelease(Key::MetaLeft) | EventType::KeyRelease(Key::MetaRight) => {
+ state.pressed_keys.remove(&Key::MetaLeft);
+ }
+ EventType::KeyPress(Key::KeyV) => {
+ if state.pressed_keys.contains(&Key::MetaLeft) {
+ state.pressed_keys.clear();
+ if let Some(window) = app_handle.get_webview_window("main") {
+ let _ = window.show();
+ let _ = window.set_focus();
+ center_window_on_current_monitor(&window);
+ }
+ }
+ }
+ _ => {}
+ }
+}
+
+fn handle_keybind_capture(app_handle: &tauri::AppHandle, event: Event, state: &mut KeybindState) {
+ match event.event_type {
+ EventType::KeyPress(key) => {
+ state.pressed_keys.insert(key);
+ update_captured_keybind(app_handle, &state.pressed_keys);
+ }
+ EventType::KeyRelease(key) => {
+ state.pressed_keys.remove(&key);
+ }
+ _ => {}
+ }
+}
+
+fn update_captured_keybind(app_handle: &tauri::AppHandle, pressed_keys: &HashSet) {
+ let modifiers: Vec = vec![Key::ControlLeft, Key::ShiftLeft, Key::Alt, Key::MetaLeft]
+ .into_iter()
+ .filter(|key| pressed_keys.contains(key))
+ .map(|key| key_to_string(key))
+ .collect();
+
+ let key = pressed_keys.iter()
+ .find(|&&key| !vec![Key::ControlLeft, Key::ShiftLeft, Key::Alt, Key::MetaLeft].contains(&key))
+ .map(|&key| key_to_string(key));
+
+ if let Some(key) = key {
+ let captured_keybind = CapturedKeybind {
+ modifiers,
+ key,
+ };
+ if let Err(e) = app_handle.emit("keybind_captured", captured_keybind) {
+ eprintln!("Error emitting keybind_captured event: {:?}", e);
+ }
+ }
+}
+
+fn key_to_string(key: Key) -> String {
+ match key {
+ Key::ControlLeft | Key::ControlRight => "Ctrl".to_string(),
+ Key::ShiftLeft | Key::ShiftRight => "Shift".to_string(),
+ Key::Alt => "Alt".to_string(),
+ Key::MetaLeft | Key::MetaRight => "Meta".to_string(),
+ _ => format!("{:?}", key),
+ }
+}
+
+#[tauri::command]
+pub fn start_keybind_capture() {
+ IS_CAPTURING_KEYBIND.store(true, Ordering::SeqCst);
+}
+
+#[tauri::command]
+pub fn stop_keybind_capture() {
+ IS_CAPTURING_KEYBIND.store(false, Ordering::SeqCst);
+}
+
+#[tauri::command]
+pub fn get_current_keybind() -> String {
+ // Implement logic to retrieve the current keybind from your configuration
+ "Meta+V".to_string() // Placeholder
+}
+
+#[tauri::command]
+pub fn save_keybind(keybind: String) -> Result<(), String> {
+ // Implement logic to save the new keybind to your configuration
+ println!("Saving keybind: {}", keybind);
+ Ok(())
}
\ No newline at end of file
diff --git a/src-tauri/src/api/tray.rs b/src-tauri/src/api/tray.rs
index d279774..6b78b8c 100644
--- a/src-tauri/src/api/tray.rs
+++ b/src-tauri/src/api/tray.rs
@@ -1,13 +1,10 @@
use tauri::{
- Manager,
- menu::{MenuBuilder, MenuItemBuilder},
- tray::{MouseButton, TrayIconBuilder, TrayIconEvent},
+ menu::{MenuBuilder, MenuItemBuilder}, tray::TrayIconBuilder, Emitter, Manager
};
pub fn setup(app: &mut tauri::App) -> Result<(), Box> {
let window = app.get_webview_window("main").unwrap();
let window_clone_for_tray = window.clone();
- let window_clone_for_click = window.clone();
let icon_bytes = include_bytes!("../../icons/Square71x71Logo.png");
let icon = tauri::image::Image::from_bytes(icon_bytes).unwrap();
@@ -15,7 +12,11 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box> {
let _tray = TrayIconBuilder::new()
.menu(
&MenuBuilder::new(app)
+ .items(&[&MenuItemBuilder::with_id("app_name", "Qopy")
+ .enabled(false)
+ .build(app)?])
.items(&[&MenuItemBuilder::with_id("show", "Show/Hide").build(app)?])
+ .items(&[&MenuItemBuilder::with_id("keybind", "Change keybind").build(app)?])
.items(&[&MenuItemBuilder::with_id("quit", "Quit").build(app)?])
.build()?,
)
@@ -31,23 +32,15 @@ pub fn setup(app: &mut tauri::App) -> Result<(), Box> {
window_clone_for_tray.show().unwrap();
window_clone_for_tray.set_focus().unwrap();
}
+ window_clone_for_tray.emit("main_route", ()).unwrap();
+ }
+ "keybind" => {
+ window_clone_for_tray.emit("change_keybind", ()).unwrap();
}
_ => (),
})
- .on_tray_icon_event(move |_tray, event| {
- if let TrayIconEvent::Click { button, .. } = event {
- if button == MouseButton::Left {
- let is_visible = window_clone_for_click.is_visible().unwrap();
- if is_visible {
- window_clone_for_click.hide().unwrap();
- } else {
- window_clone_for_click.show().unwrap();
- }
- }
- }
- })
.icon(icon)
.build(app)?;
Ok(())
-}
\ No newline at end of file
+}
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index d55dc7e..07b47a8 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -31,6 +31,7 @@ fn main() {
.setup(|app| {
let app_handle = app.handle().clone();
+ // #[cfg(not(target_os = "macos"))]
api::hotkeys::setup(app_handle.clone());
api::tray::setup(app)?;
api::database::setup(app)?;
@@ -73,6 +74,10 @@ fn main() {
api::clipboard::get_image_path,
api::clipboard::write_and_paste,
api::clipboard::read_image,
+ api::hotkeys::start_keybind_capture,
+ api::hotkeys::stop_keybind_capture,
+ api::hotkeys::get_current_keybind,
+ api::hotkeys::save_keybind,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");