├── src ├── index.js ├── dbus-connection.js └── runner.js ├── joplin-runner.service ├── updateConfig.sh ├── config.js ├── uninstall.sh ├── plasma-runner-joplin.desktop ├── package.json ├── install.sh ├── .gitignore └── README.md /src/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require("./runner"); 3 | -------------------------------------------------------------------------------- /joplin-runner.service: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=net.belka.joplin 3 | Exec=%{BASE_DIR}/.local/share/kservices5/joplin-runner/src/index.js -------------------------------------------------------------------------------- /updateConfig.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # run after you update config.js 4 | 5 | rm $HOME/.local/share/kservices5/joplin-runner/config.js 6 | cp config.js $HOME/.local/share/kservices5/joplin-runner/ 7 | pgrep -f "$HOME/.local/share/kservices5/joplin-runner/src/index.js" | grep -v $$ | xargs kill &>/dev/null -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | runner: { 3 | show: "10", 4 | order_by: "created_time", // title, created_time, updated_time... 5 | order_dir: "ASC", //ASC, DESC 6 | prefix: "note", 7 | joplinPath: "" // Path to run Joplin, either a command or an AppImage 8 | }, 9 | webClipper: { 10 | port: "41184", 11 | token: "" 12 | }, 13 | sendAction: { 14 | port: "42420" 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | 4 | pgrep -f "$HOME/.local/share/kservices5/joplin-runner/src/index.js" | grep -v $$ | xargs kill &>/dev/null 5 | 6 | [ -d "$HOME/.local/share/kservices5/joplin-runner/" ] && rm -r $HOME/.local/share/kservices5/joplin-runner/; 7 | 8 | [ -f "$HOME/.local/share/dbus-1/services/joplin-runner.service" ] && rm $HOME/.local/share/dbus-1/services/joplin-runner.service; 9 | 10 | kquitapp5 krunner 11 | 12 | 13 | printf "Done." 14 | 15 | -------------------------------------------------------------------------------- /plasma-runner-joplin.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=joplin 3 | Comment=Krunner plugin to open and create notes 4 | X-KDE-ServiceTypes=Plasma/Runner 5 | Type=Service 6 | Icon=joplin 7 | X-KDE-PluginInfo-Author=BelkaDev 8 | X-KDE-PluginInfo-Email=belk5@hotmail.com 9 | X-KDE-PluginInfo-Name=joplin 10 | X-KDE-PluginInfo-Version=0.1 11 | X-KDE-PluginInfo-License=GPL-3.0 12 | X-KDE-PluginInfo-EnabledByDefault=true 13 | X-Plasma-API=DBus 14 | X-Plasma-DBusRunner-Service=net.belka.joplin 15 | X-Plasma-DBusRunner-Path=/joplin -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "joplin", 3 | "version": "0.0.2", 4 | "description": "A krunner plugin to open and create notes", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "bundle-dependencies": "mkdir -p bin;makeself --tar-extra \"--exclude=*.md --exclude=package-lock.json --exclude=.vscode --exclude=.idea --exclude=bin --exclude=dist --exclude=*.desktop --exclude-vcs\" . joplin.run joplin node src/index.js; mv joplin.run bin/", 9 | "bundle-standalone": "pkg src/index.js --target=node12-linux-x64 --output ./bin/joplin-standalone.run" 10 | }, 11 | "author": "belka", 12 | "license": "GPL-3.0", 13 | "dependencies": { 14 | "dbus-native": "^0.4.0", 15 | "node-fetch": "^2.6.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | set -eE -o functrace 4 | 5 | log_err() { 6 | local lineno=$1;local msg=$2 7 | printf "\nError at $lineno: $msg" 8 | } 9 | 10 | trap 'log_err ${LINENO} "$BASH_COMMAND"' ERR 11 | 12 | [[ -z $(xdotool -v) ]] && echo "Dependency Xdotool was not found. Exiting." && exit 1 13 | 14 | [ ! -d "$HOME/.local/share/kservices5/" ] && mkdir -p $HOME/.local/share/kservices5/ 15 | [ ! -d "$HOME/.local/share/dbus-1/services/" ] && mkdir -p $HOME/.local/share/dbus-1/services/ 16 | 17 | [ ! -f "plasma-runner-joplin.desktop" ] && git clone "https://github.com/BelkaDev/KRunner-joplin-plugin" "KRunner-joplin-plugin" && cd KRunner-joplin-plugin 18 | 19 | npm install 20 | 21 | chmod +x src/index.js 22 | 23 | rsync -a --exclude=".*" --exclude "assets" . ~/.local/share/kservices5/joplin-runner 24 | 25 | sed "s|%{BASE_DIR}|$HOME|g" joplin-runner.service > ~/.local/share/dbus-1/services/joplin-runner.service 26 | 27 | kquitapp5 krunner 28 | 29 | printf "\nInstallation complete." 30 | -------------------------------------------------------------------------------- /src/dbus-connection.js: -------------------------------------------------------------------------------- 1 | const dbus = require("dbus-native"); 2 | 3 | const sessionBus = dbus.sessionBus(); 4 | if (!sessionBus) throw new Error('Could not connect to session bus'); 5 | sessionBus.requestName("net.belka.joplin", 0x04, (err, code) => { 6 | if (err) throw new Error(err); 7 | 8 | if (code === 3) throw new Error(`Another instance is already running`); 9 | if (code !== 1) { 10 | throw new Error( 11 | `Received code ${code} while requesting service name "net.belka.joplin"` 12 | ); 13 | process.exit(1) 14 | } 15 | }); 16 | 17 | module.exports.createKRunnerInterface = ({ 18 | path, 19 | actionsFunction, 20 | runFunction, 21 | matchFunction 22 | }) => { 23 | const interface = {}; 24 | const interfaceDesc = { 25 | name: "org.kde.krunner1", 26 | methods: {} 27 | }; 28 | 29 | if (actionsFunction) { 30 | interface.Actions = actionsFunction; 31 | interfaceDesc.methods.Actions = ["", "a(sss)", [], ["matches"]]; 32 | } 33 | 34 | if (runFunction) { 35 | interface.Run = runFunction; 36 | interfaceDesc.methods.Run = ["ss", "", ["matchId", "actionId"], []]; 37 | } 38 | 39 | if (matchFunction) { 40 | interface.Match = matchFunction; 41 | interfaceDesc.methods.Match = [ 42 | "s", 43 | "a(sssida{sv})", 44 | ["query"], 45 | ["matches"] 46 | ]; 47 | } 48 | 49 | sessionBus.exportInterface(interface, path, interfaceDesc); 50 | }; 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/node,visualstudiocode 2 | # Edit at https://www.gitignore.io/?templates=node,visualstudiocode 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Optional REPL history 61 | .node_repl_history 62 | 63 | # Output of 'npm pack' 64 | *.tgz 65 | 66 | # Yarn Integrity file 67 | .yarn-integrity 68 | 69 | # dotenv environment variables file 70 | .env 71 | .env.test 72 | 73 | # parcel-bundler cache (https://parceljs.org/) 74 | .cache 75 | 76 | # next.js build output 77 | .next 78 | 79 | # nuxt.js build output 80 | .nuxt 81 | 82 | # react / gatsby 83 | public/ 84 | 85 | # vuepress build output 86 | .vuepress/dist 87 | 88 | # Serverless directories 89 | .serverless/ 90 | 91 | # FuseBox cache 92 | .fusebox/ 93 | 94 | # DynamoDB Local files 95 | .dynamodb/ 96 | 97 | ### VisualStudioCode ### 98 | .vscode/* 99 | !.vscode/settings.json 100 | !.vscode/tasks.json 101 | !.vscode/launch.json 102 | !.vscode/extensions.json 103 | 104 | ### VisualStudioCode Patch ### 105 | # Ignore all local history of files 106 | .history 107 | 108 | # End of https://www.gitignore.io/api/node,visualstudiocode 109 | 110 | # Build files 111 | bin 112 | 113 | config.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # KRunner-Joplin-plugin 3 | 4 | This plugin uses DBus to communicate with KRunner. 5 | 6 | https://user-images.githubusercontent.com/49416514/126008779-d9b0d368-8517-4d2c-ae8a-4f6173c43cdb.mp4 7 | 8 | 9 | Usage: 10 | - Search all notes: ` :` 11 | - Search all notes inside a notebook ` /:`
12 | [Filters reference](https://joplinapp.org/help/#search-filters) from Joplin API.
13 | 14 | Requirements: 15 | - Xdotool 16 | 17 | ## Installation 18 | 19 | ### Joplin Send Action plugin 20 | First, you need to install the latest released JPL plugin package (*.jpl) from [here](https://github.com/BelkaDev/joplin-send-action-plugin/releases/tag/V1.0.0). Then: 21 | - Cmd : 22 | ```bash 23 | # Replace config file with your own 24 | cp com.joplin.sendAction.jpl ~/.config/joplin-desktop/plugins 25 | ``` 26 | - Manually 27 | 28 | * Open Joplin and navigate to Tools > Options > Plugins 29 | * Press Install plugin and select the previously downloaded jpl file 30 | * Confirm selection 31 |
32 | 33 | Restart Joplin to enable the plugin. 34 | *** 35 | ### Krunner plugin 36 | ##### CMD 37 | ``` bash 38 | curl -s https://raw.githubusercontent.com/BelkaDev/KRunner-Joplin-plugin/master/install.sh | sh 39 | ### Or 40 | git clone https://github.com/BelkaDev/KRunner-joplin-plugin 41 | cd KRunner-joplin-plugin 42 | chmod +x install.sh && ./install.sh 43 | ``` 44 | Important: The runner will not work without changing `config.js`
45 | You can fetch notes from a daemonized Joplin instance using this command (run on startup): 46 | ```bash 47 | # Replace config file with your own 48 | joplin --profile ~/.config/joplin-desktop/ server start 49 | ``` 50 | 51 | ##### Manual 52 | ```bash 53 | git clone https://github.com/BelkaDev/KRunner-joplin-plugin 54 | cd KRunner-joplin-plugin 55 | npm install 56 | chmod +x src/index.js 57 | ``` 58 | * Edit `Config.js` File 59 | * Copy folder to `~/.local/share/kservices5/joplin-runner` 60 | * Copy file `joplin-runner.service` to `~/.local/share/dbus-1/services/joplin-runner.service` (change ${BASE_DIR} first) 61 | ```bash 62 | kquitapp5 krunner 63 | ``` 64 | *** 65 | ## Features 66 | * Search, open, create new notes. 67 | * Output formatting. 68 | * Send focus to client upon selection. 69 | * Launch Joplin if it's not running initially 70 | 71 | 72 | ## Troubleshooting 73 | ### Notes aren't showing up 74 | * Make sure a joplin client or daemon is running in server mode (enable webClipper) 75 | To run joplin daemon: 76 | ```bash 77 | joplin --profile ~/.config/joplin-desktop/ server start 78 | ``` 79 | * Make sure you have an active Dbus session running with this command 80 | ```bash 81 | env | grep DBUS_SESSION_BUS_ADDRESS 82 | ``` 83 | If empty, add it manually to your shell settings file: 84 | ```bash 85 | echo "export $(dbus-launch | head -n1)" >> ~/.bashrc 86 | ``` 87 | 88 | ### Selection/Focus isn't working 89 | * Check that the config settings are matching to the plugins. 90 | 91 | ### Config file isn't updating 92 | * Run `updateSettings.sh` to update your config changes. 93 | 94 | ### I want to delete the runner 95 | * Run `uninstall.sh` 96 | -------------------------------------------------------------------------------- /src/runner.js: -------------------------------------------------------------------------------- 1 | const dbus = require("dbus-native"); 2 | const fetch = require("node-fetch"); 3 | const config = require("../config.js"); 4 | const exec = require("child_process").exec; 5 | const { createKRunnerInterface } = require("./dbus-connection"); 6 | 7 | 8 | const execAsync = cmd => { 9 | return new Promise((resolve, reject) => { 10 | exec(cmd, (error, stdout, stderr) => { 11 | if (error) console.warn(error); 12 | !(stderr||error) 13 | ? resolve(...stdout.split('\n')) 14 | : reject(new Error(`${stderr} ${error}`)); 15 | }); 16 | }); 17 | }; 18 | 19 | 20 | const sessionBus = dbus.sessionBus(); 21 | if (!sessionBus) throw new Error("Failed to connect to the session bus"); 22 | 23 | createKRunnerInterface({ 24 | path: "/joplin", 25 | async runFunction(match,action) { 26 | 27 | const [note, folder] = match.split(":"); 28 | 29 | const path = folder 30 | ? `http://127.0.0.1:${config.sendAction.port}/notes/create?folderId=${folder}¬eTitle=${note}` 31 | : `http://127.0.0.1:${config.sendAction.port}/notes/open/${note}`; 32 | 33 | 34 | execAsync(`echo "$(xdotool search --onlyvisible --class joplin)"`) 35 | .then(async (WID) => { 36 | fetch(path).then( () => { 37 | // Focus window 38 | execAsync(`xdotool windowactivate ${WID}`) 39 | }).catch(async ex => { 40 | // Open client 41 | var proc = require("child_process").spawn(config.runner.joplinPath, [] , {detached:true, stdio:['ignore']}); 42 | proc.unref(); 43 | 44 | execAsync(`until WID="$(xdotool search --onlyvisible --class joplin)" 45 | do sleep 1; done; printf "$WID"`).then(async (WID) => { 46 | execAsync("sleep 2s").then(()=>{ // wait for client/plugin to init 47 | fetch(path).then((x)=>{ 48 | // Focus window 49 | execAsync(`xdotool windowactivate ${WID}`).catch() 50 | }).catch((e) => 51 | console.error(e) 52 | ); 53 | }); 54 | 55 | }); 56 | });; 57 | }) 58 | }, 59 | async matchFunction(query,log) { 60 | if (query.replace(/ .*/, "") !== config.runner.prefix) matchFunction(); 61 | 62 | query = query 63 | .split(/\s/) 64 | .slice(1) 65 | .join(" "); 66 | 67 | query = query === "" ? "-" : query; 68 | 69 | let folder = ""; 70 | if (/\//.test(query)) { 71 | folder = `${query.split("/")[0]}`; 72 | query = `notebook:${folder} ${query.split("/")[1]}`; 73 | } 74 | 75 | let results = await fetch( 76 | `http://127.0.0.1:${config.webClipper.port}/search?query=${query}\ 77 | &order_by=${config.runner.order_by}\ 78 | &order_dir=${config.runner.order_dir}\ 79 | &limit=${config.runner.show}\ 80 | &token=${config.webClipper.token}` 81 | ) 82 | .then(function(res) { 83 | return res.json(); 84 | }) 85 | .then(function(data) { 86 | return data.items; 87 | }); 88 | let notes = results.map(async note => { 89 | await fetch( 90 | `http://127.0.0.1:${config.webClipper.port}/folders/${note.parent_id}?token=${config.webClipper.token}` 91 | ) 92 | .then(res => { 93 | return res.json(); 94 | }) 95 | .then(notebook => { 96 | note.title = `[${notebook.title}] ${note.title}`; 97 | }); 98 | return note; 99 | }); 100 | notes = await Promise.all(notes).then(note => { 101 | return note; 102 | }); 103 | 104 | const output = notes.map(note => { 105 | return [note.id, note.title, "joplin", 50, 10, {}]; 106 | }); 107 | 108 | query = /:/.test(query) ? query.split(":")[query.split(":").length -1] : query; // ignore filters 109 | if (folder) { 110 | console.log(folder) 111 | const searchFolder = await fetch( 112 | `http://127.0.0.1:${config.webClipper.port}/search?query=${folder}&type=folder&token=${config.webClipper.token}` 113 | ); 114 | const notebook = await searchFolder.json(); 115 | if (notebook.items) 116 | output.push([ 117 | `${query.replace(folder+" ", "")}:${notebook.items[0].id}`, 118 | `Create new note "${query.replace(folder+" ", "")}" in ${ 119 | notebook.items[0].title 120 | }`, 121 | "joplin", 122 | 50, 123 | 10, 124 | {} 125 | ]); 126 | } 127 | return output; 128 | } 129 | }); 130 | --------------------------------------------------------------------------------