This commit is contained in:
2026-08-05 04:37:32 +09:00
parent 33e09f8e93
commit a0ae83d24e
8 changed files with 120 additions and 124 deletions
+1 -1
View File
@@ -125,7 +125,7 @@ in
(execute "W-l" "loginctl lock-session") (execute "W-l" "loginctl lock-session")
(execute "W-v" "vicinae vicinae://launch/clipboard/history?toggle=true") (execute "W-v" "vicinae vicinae://launch/clipboard/history?toggle=true")
(execute "W-j" "nani-translate-primary") (execute "W-j" "nani-translate-primary")
(execute "W-S-j" "normcap-translate") (execute "W-S-j" "nani-translate-ocr")
(execute "W-C-j" "${lib.getExe' pkgs.xdg-utils "xdg-open"} naniapp://translate") (execute "W-C-j" "${lib.getExe' pkgs.xdg-utils "xdg-open"} naniapp://translate")
(execute "W-space" "ghostty +toggle-quick-terminal") (execute "W-space" "ghostty +toggle-quick-terminal")
(execute "W-p" "wdisplays") (execute "W-p" "wdisplays")
+74 -1
View File
@@ -1,5 +1,78 @@
_: { { pkgs, ... }:
let
tessdataBest = pkgs.runCommand "tessdata-best-jpn-eng-chi-sim" { } ''
mkdir -p "$out"
ln -s ${
pkgs.fetchurl {
url = "https://github.com/tesseract-ocr/tessdata_best/raw/main/jpn.traineddata";
hash = "sha256-Nr35rII/WRHmJMMNBVPokLirx8MaZbPvFNqUNljEC3k=";
}
} "$out/jpn.traineddata"
ln -s ${
pkgs.fetchurl {
url = "https://github.com/tesseract-ocr/tessdata_best/raw/main/eng.traineddata";
hash = "sha256-goCu0Hgv4nJXpo6hD+fvMkyg+Nhb0v0UXRwrVgvLZro=";
}
} "$out/eng.traineddata"
ln -s ${
pkgs.fetchurl {
url = "https://github.com/tesseract-ocr/tessdata_best/raw/main/chi_sim.traineddata";
hash = "sha256-T+8tEwbI6HYW1NPkxsZ/r11EvjNCKQz48vD246p+c1s=";
}
} "$out/chi_sim.traineddata"
'';
tesseract = pkgs.tesseract5.override {
tessdata = tessdataBest;
};
naniTranslatePrimary = pkgs.writeShellApplication {
name = "nani-translate-primary";
runtimeInputs = with pkgs; [
jq
wl-clipboard
xdg-utils
];
text = ''
selected_text="$(wl-paste --primary --no-newline)" || exit 0
[ -n "$selected_text" ] || exit 0
encoded_text="$(printf '%s' "$selected_text" | jq -sRr @uri)"
exec xdg-open "naniapp://translate?source=$encoded_text"
'';
};
naniTranslateOcr = pkgs.writeShellApplication {
name = "nani-translate-ocr";
runtimeInputs = with pkgs; [
grim
jq
slurp
tesseract
xdg-utils
];
text = ''
geometry="$(slurp)" || exit 0
captured_text="$(
grim -g "$geometry" - \
| tesseract stdin stdout -l jpn+eng+chi_sim --oem 1 --psm 6
)"
[ -n "$captured_text" ] || exit 0
encoded_text="$(printf '%s' "$captured_text" | jq -sRr @uri)"
exec xdg-open "naniapp://translate?source=$encoded_text"
'';
};
in
{
programs.naniTranslateLinux.enable = true; programs.naniTranslateLinux.enable = true;
xdg.mimeApps.defaultApplications."x-scheme-handler/naniapp" = "nani.desktop"; xdg.mimeApps.defaultApplications."x-scheme-handler/naniapp" = "nani.desktop";
home.packages = [
naniTranslatePrimary
naniTranslateOcr
];
} }
+1 -17
View File
@@ -1,24 +1,8 @@
{ lib, pkgs, ... }: { lib, pkgs, ... }:
let let
keybindings = import ./keybindings.nix; keybindings = import ./keybindings.nix;
naniTranslatePrimary = pkgs.writeShellApplication {
name = "nani-translate-primary";
runtimeInputs = with pkgs; [
jq
wl-clipboard
xdg-utils
];
text = ''
selected_text="$(wl-paste --primary --no-newline)" || exit 0
[ -n "$selected_text" ] || exit 0
encoded_text="$(printf '%s' "$selected_text" | jq -sRr @uri)"
exec xdg-open "naniapp://translate?source=$encoded_text"
'';
};
in in
{ {
home.packages = [ naniTranslatePrimary ];
programs.niri.settings = { programs.niri.settings = {
binds = keybindings // { binds = keybindings // {
@@ -119,7 +103,7 @@ in
}; };
"Mod+Shift+J" = { "Mod+Shift+J" = {
repeat = false; repeat = false;
action.spawn = [ "normcap-translate" ]; action.spawn = [ "nani-translate-ocr" ];
hotkey-overlay.title = "OCR and Translate with Nani"; hotkey-overlay.title = "OCR and Translate with Nani";
}; };
"Mod+Ctrl+J" = { "Mod+Ctrl+J" = {
@@ -0,0 +1,43 @@
diff --git a/src/shell/bar/widgets/taskbar_widget.cpp b/src/shell/bar/widgets/taskbar_widget.cpp
--- a/src/shell/bar/widgets/taskbar_widget.cpp
+++ b/src/shell/bar/widgets/taskbar_widget.cpp
@@ -5,6 +5,7 @@
#include "compositors/workspace_backend.h"
#include "config/config_service.h"
#include "core/deferred_call.h"
+#include "core/process/process.h"
#include "i18n/i18n.h"
#include "render/core/color.h"
#include "render/core/renderer.h"
@@ -36,6 +37,18 @@
namespace {
+ [[nodiscard]] bool launchTaskbarOverview(const std::string& appId) {
+ constexpr const char* command = "noctalia-taskbar-overview";
+ if (appId.empty() || !process::commandExists(command)) {
+ return false;
+ }
+ return process::runAsync({
+ command,
+ "--app-id",
+ appId.c_str(),
+ });
+ }
+
// Integer centering; optional odd spare pixel on the end side (right/bottom).
[[nodiscard]] float centeredOffset(float extent, float content, float inset = 0.0F, bool oddSpareOnEnd = true) {
const float inner = std::max(0.0F, extent - inset * 2.0F);
@@ -373,6 +386,12 @@ void TaskbarWidget::activateOrLaunchPinned(const TaskModel& task) {
return;
}
+ const std::string overviewAppId =
+ !task.appId.empty() ? task.appId : (!task.desktopEntryId.empty() ? task.desktopEntryId : task.idLower);
+ if (launchTaskbarOverview(overviewAppId)) {
+ return;
+ }
+
const std::string cycleKey = !task.desktopEntryId.empty() ? task.desktopEntryId : task.idLower;
std::size_t& cursor = m_groupedAppCycleCursor[cycleKey];
if (cursor >= windows.size()) {
@@ -24,56 +24,3 @@ diff --git a/src/shell/switcher/window_switcher.cpp b/src/shell/switcher/window_
if (key.empty()) { if (key.empty()) {
continue; continue;
} }
diff --git a/src/shell/bar/widgets/taskbar_widget.cpp b/src/shell/bar/widgets/taskbar_widget.cpp
--- a/src/shell/bar/widgets/taskbar_widget.cpp
+++ b/src/shell/bar/widgets/taskbar_widget.cpp
@@ -5,6 +5,7 @@
#include "compositors/workspace_backend.h"
#include "config/config_service.h"
#include "core/deferred_call.h"
+#include "core/process/process.h"
#include "i18n/i18n.h"
#include "render/core/color.h"
#include "render/core/renderer.h"
@@ -36,6 +37,18 @@
namespace {
+ [[nodiscard]] bool launchTaskbarOverview(const std::string& appId) {
+ constexpr const char* command = "noctalia-taskbar-overview";
+ if (appId.empty() || !process::commandExists(command)) {
+ return false;
+ }
+ return process::runAsync({
+ command,
+ "--app-id",
+ appId.c_str(),
+ });
+ }
+
// Integer centering; optional odd spare pixel on the end side (right/bottom).
[[nodiscard]] float centeredOffset(float extent, float content, float inset = 0.0F, bool oddSpareOnEnd = true) {
const float inner = std::max(0.0F, extent - inset * 2.0F);
@@ -373,6 +386,12 @@ void TaskbarWidget::activateOrLaunchPinned(const TaskModel& task) {
return;
}
+ const std::string overviewAppId =
+ !task.appId.empty() ? task.appId : (!task.desktopEntryId.empty() ? task.desktopEntryId : task.idLower);
+ if (launchTaskbarOverview(overviewAppId)) {
+ return;
+ }
+
const std::string cycleKey = !task.desktopEntryId.empty() ? task.desktopEntryId : task.idLower;
std::size_t& cursor = m_groupedAppCycleCursor[cycleKey];
if (cursor >= windows.size()) {
@@ -792,6 +811,9 @@ void TaskbarWidget::create() {
}
if (data.button == BTN_LEFT) {
if (!cycleCandidates.empty()) {
+ if (cycleCandidates.size() > 1 && launchTaskbarOverview(current->appId)) {
+ return;
+ }
std::size_t& cursor = m_groupedAppCycleCursor[cycleKey];
if (cursor >= cycleCandidates.size()) {
cursor = 0;
@@ -1,44 +0,0 @@
{ pkgs, ... }:
let
normcap = pkgs.normcap.override {
tesseract4 = pkgs.tesseract4.override {
enableLanguages = [
"chi_sim"
"jpn"
"eng"
];
};
};
normcapTranslate = pkgs.writeShellApplication {
name = "normcap-translate";
runtimeInputs = [
normcap
pkgs.jq
pkgs.xdg-utils
];
text = ''
captured_text="$(
normcap \
--cli-mode \
--screenshot-handler grim \
--show-introduction False \
--update False \
--detect-codes False \
--notification False \
-l jpn eng chi_sim
)" || exit 0
[ -n "$captured_text" ] || exit 0
encoded_text="$(printf '%s' "$captured_text" | jq -sRr @uri)"
exec xdg-open "naniapp://translate?source=$encoded_text"
'';
};
in
{
home.packages = [
normcap
normcapTranslate
];
}
-7
View File
@@ -1,7 +0,0 @@
{
description = "NormCap OCR screen capture";
includes = [
"applications.nani"
];
}
@@ -13,7 +13,7 @@
"applications.loupe" "applications.loupe"
"applications.linux-wallpaperengine" "applications.linux-wallpaperengine"
"applications.nautilus" "applications.nautilus"
"applications.normcap" "applications.nani"
"applications.papers" "applications.papers"
"applications.qalculate-gtk" "applications.qalculate-gtk"
"applications.resources" "applications.resources"