├── README.md └── docs ├── airc.md ├── audiolex.md ├── close.md ├── cmd_icons.md ├── commands.md ├── doubletap.md ├── img ├── Screenshot_20241128_042132.jpg ├── Screenshot_20241128_042239.jpg └── audiolex.jpg ├── jsondb.md ├── jstyle.md ├── mouse.md ├── nextwatch.md ├── plane.md ├── rofi_dev.md ├── symview.md ├── timers.md └── util_screen.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | --- 4 | 5 | 1) [Rofi](https://github.com/madprops/blog/blob/main/docs/rofi_dev.md) 6 | 7 | 1) [Timers](https://github.com/madprops/blog/blob/main/docs/timers.md) 8 | 9 | 1) [UtilScreen](https://github.com/madprops/blog/blob/main/docs/util_screen.md) 10 | 11 | 1) [Mouse](https://github.com/madprops/blog/blob/main/docs/mouse.md) 12 | 13 | 1) [Symview](https://github.com/madprops/blog/blob/main/docs/symview.md) 14 | 15 | 1) [airc](https://github.com/madprops/blog/blob/main/docs/airc.md) 16 | 17 | 1) [JStyle](https://github.com/madprops/blog/blob/main/docs/jstyle.md) 18 | 19 | 1) [Close](https://github.com/madprops/blog/blob/main/docs/close.md) 20 | 21 | 1) [JsonDB](https://github.com/madprops/blog/blob/main/docs/jsondb.md) 22 | 23 | 1) [DoubleTap](https://github.com/madprops/blog/blob/main/docs/doubletap.md) 24 | 25 | 1) [Commands](https://github.com/madprops/blog/blob/main/docs/commands.md) 26 | 27 | 1) [AudioLex](https://github.com/madprops/blog/blob/main/docs/audiolex.md) 28 | 29 | 1) [Nextwatch](https://github.com/madprops/blog/blob/main/docs/nextwatch.md) 30 | 31 | 1) [ThePlane](https://github.com/madprops/blog/blob/main/docs/plane.md) 32 | 33 | 1) [CmdIcons](https://github.com/madprops/blog/blob/main/docs/cmd_icons.md) -------------------------------------------------------------------------------- /docs/airc.md: -------------------------------------------------------------------------------- 1 | # airc (ai + irc) 2 | 3 | Around 2 weeks ago I decided to finally give in to the novelty of chatGPT. 4 | 5 | I decided to make an irc bot that would make requests to the openai api. 6 | 7 | I thought it could become some sort of consultant people in my irc channel can ask questions to. 8 | 9 | I signed up for an account and decided to make a simple bot in nodejs. 10 | 11 | After two weeks of development this is what I have to say. 12 | 13 | I'm not going to go deep into how it's made, just how we use it. 14 | 15 | The model is `text-davinci-003`. 16 | 17 | airc es software libre: https://github.com/madprops/airc 18 | 19 | (Info below is about the first version of airc) 20 | 21 | (Some things have changed) 22 | 23 | --- 24 | 25 | So I set up the first bot, called Arc. 26 | 27 | We started by asking it questions. 28 | 29 | ![](https://i.imgur.com/zHeo8pr.jpg) 30 | 31 | Checking its logic. We found it good but also found it can contradict itself. 32 | 33 | Some information was straight up incorrect. 34 | 35 | --- 36 | 37 | Playing a bit with it more. 38 | 39 | ![](https://i.imgur.com/6pi0Zux.jpg) 40 | 41 | --- 42 | 43 | Checked its multi-language capabilities 44 | 45 | ![](https://i.imgur.com/aS60bHL.jpg) 46 | 47 | --- 48 | 49 | Then this is when it got really fun. 50 | 51 | I set up another bot called Rupert. 52 | 53 | Rupert was meant to have a specific personality. 54 | 55 | Specifically: `Please respond in the style of Stewie from Family Guy`. 56 | 57 | That was prepended before every prompt. 58 | 59 | ![](https://i.imgur.com/Vs5O1O3.jpg) 60 | 61 | --- 62 | 63 | We found it was really easy to change the personality. 64 | 65 | All it needs are simple instructions. 66 | 67 | ![](https://i.imgur.com/pcsfMAr.jpg) 68 | 69 | --- 70 | 71 | Realized having hardcoded instructions or 72 | telling it how to act everytime was not flexible/easy enough. 73 | 74 | So I decided to add commands to the bots. 75 | 76 | ![](https://i.imgur.com/pgBYWUU.jpg) 77 | 78 | ![](https://i.imgur.com/xXlvdSF.jpg) 79 | 80 | ![](https://i.imgur.com/ecroNHt.jpg) 81 | 82 | Here's The Undertaker: 83 | 84 | ![](https://i.imgur.com/W7XIhgf.jpg) 85 | 86 | --- 87 | 88 | Currently it has these commands: 89 | 90 | ![](https://i.imgur.com/66ufytw.jpg) 91 | 92 | `!ur` is a shortcut to set a personality. 93 | 94 | It changes the bot's `rules`. Which are prepended before every prompt. 95 | 96 | You just do `bot: !ur an angry duck` 97 | 98 | It's a shortcut to set the rules to: 99 | 100 | `Respond as if you were an angry duck` 101 | 102 | Or you can use the `!rules` command and set something directly. 103 | 104 | It has `users` and `admins`. 105 | 106 | Admins can run all the commands. 107 | 108 | The purpose of `users` is if you want to whitelist access to the bot. 109 | 110 | It defaults to allowing everyone to ask and modify the bot's personality. 111 | 112 | You can change those two permissions through commands. 113 | 114 | --- 115 | 116 | Every prompt is standalone, there is no context/history. 117 | 118 | This makes the bot cheaper to use. And maybe less confusing. 119 | 120 | But sometimes you want some context. 121 | 122 | So I added a way to reference the previous message. 123 | 124 | ![](https://i.imgur.com/jarl405.jpg) 125 | 126 | Just use `^` at the start of the message. 127 | 128 | --- 129 | 130 | So yes, we have Arc as the vanilla ai consultant for serious questions. 131 | 132 | And we have Rupert to program it to act as anything! 133 | 134 | It's been actually really fun. 135 | 136 | ![](https://i.imgur.com/gCTy8hF.jpg) -------------------------------------------------------------------------------- /docs/audiolex.md: -------------------------------------------------------------------------------- 1 | ![](img/audiolex.jpg) -------------------------------------------------------------------------------- /docs/close.md: -------------------------------------------------------------------------------- 1 | # Close 2 | 3 | Lots about how to launch programs is discussed often, but what about the ways to close and hide them? 4 | 5 | I'd say the way we get rid of what we don't need at the moment anymore is equally important. 6 | 7 | ## Closing 8 | 9 | I don't use window decorations at all, so no X button to click. 10 | 11 | 90% of the time I close windows by double clicking the DPI button of my mouse. 12 | 13 | This is a button that sits at the top of the mouse, and it's meant to be used to change the DPI (sensitivity). 14 | 15 | But I mapped it to something else, and if I double click it quick enough it closes the window where the cursor is at. 16 | 17 | So I just point to a window and tap tap, done. It's really fast. 18 | 19 | ## Hiding 20 | 21 | A lot of the time I don't want to close a window, I just want to make it get out of the way. 22 | 23 | Since I don't have a minimize button I mapped a side button of my mouse to trigger a minimize. 24 | 25 | So I just point to the window and click the button. Similar to the DPI method. 26 | 27 | ## Cursor 28 | 29 | The advantage of using the "close/hide the window below the cursor" method is that the whole window is the target. 30 | 31 | I don't need to point and click on tiny areas like the minimize/close buttons, I have the whole window area to click. 32 | 33 | And not having decorations saves space, plus makes important clickable areas reside at the top, like tabs. 34 | 35 | In awesomewm getting the window below the cursor is simple: 36 | 37 | ```lua 38 | local c = mouse.object_under_pointer() 39 | ``` 40 | 41 | ## DPI 42 | 43 | Any mouse button that can be mapped works for what I do. 44 | 45 | What I do is map the button to a keyboard combo like Ctrl+Alt+Space or whatever. 46 | 47 | I do this using an open source software manager for my mouse. 48 | 49 | Then in my window manager I map the shortcut to an action. 50 | 51 | The DPI button in my mouse is handy because it's at the top, near the middle, easy to reach. 52 | 53 | ## Taps 54 | 55 | I made a library for awesomewm to detect double click/tap/press and perform an action. 56 | 57 | You can find it [here](https://github.com/madprops/awesome-setup/tree/master/madwidgets/doubletap). -------------------------------------------------------------------------------- /docs/cmd_icons.md: -------------------------------------------------------------------------------- 1 | # Command Icons 2 | 3 | I gained a new trick. 4 | 5 | I go back in shell history very often to re-run the same commands over and over. 6 | 7 | There's no efficient way to do this yet. 8 | 9 | I think this is a problem that needs to be solved with a specialized tool. 10 | 11 | I might try making some shell injection using rofi later. 12 | 13 | But for now I thought of a trick that mitigates it somewhat. 14 | 15 | Add icons to commands, so you can instantly recognize them on up arrows: 16 | 17 | `: ✅; ./utils/check.sh` 18 | 19 | `: ⚡; ./scripts/tag.py` 20 | 21 | `: 📚; ./scripts/makedocs.sh` 22 | 23 | (Notice the colons and semi-colons) 24 | 25 | The icons are not part of the command, they do nothing. 26 | 27 | But now your vision recognizes the correct item very quickly. 28 | 29 | Instead of icons you could use text which you can grep later easily. -------------------------------------------------------------------------------- /docs/commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | I was missing a way to re-run commands quickly from my terminals. 4 | 5 | I use up-arrow-rewind or autocomplete to run previous commands. 6 | 7 | But that can get annoying sometimes, I wanted something to click. 8 | 9 | So here it is: 10 | 11 | ## Fish 12 | 13 | Add this to `~/.config/fish/config.fish`: 14 | 15 | ```bash 16 | function intercept --on-event fish_postexec 17 | if [ "$status" != 0 ] 18 | return 19 | end 20 | 21 | ~/bin/save_command $argv 22 | end 23 | ``` 24 | 25 | This will run a ruby script after every succesful command. 26 | 27 | ## Ruby 28 | 29 | This is the script that gets called to update the commands file: 30 | 31 | >save_command 32 | 33 | ```ruby 34 | #!/usr/bin/env ruby 35 | if ARGV[0].nil? 36 | return 37 | end 38 | 39 | cmd = ARGV[0].strip 40 | 41 | if cmd.empty? 42 | return 43 | end 44 | 45 | xcmds_equals = ["cd", "ls", "lq", "z", "br", "o"] 46 | xcmds_starts = ["cd", "z"] 47 | 48 | if xcmds_equals.any? { |x| cmd == x } 49 | return 50 | end 51 | 52 | if xcmds_starts.any? { |x| cmd.start_with?(x) } 53 | return 54 | end 55 | 56 | xhistory = File.expand_path("~/.xhistory.log") 57 | max_xhistory = 500 58 | lines = [] 59 | 60 | File.open(xhistory, "a+") do |file| 61 | lines = file.readlines 62 | end 63 | 64 | lines.prepend(cmd) 65 | content = lines.map(&:chomp).uniq.take(max_xhistory).join("\n") 66 | 67 | File.open(xhistory, "w") do |file| 68 | file.puts(content) 69 | end 70 | ``` 71 | 72 | This is the script that opens `rofi` to pick a command: 73 | 74 | >commands 75 | 76 | ```ruby 77 | #!/usr/bin/env ruby 78 | require "open3" 79 | 80 | def pick_cmd(prompt, data) 81 | cmd = "rofi -dmenu -p '#{prompt}' -me-select-entry '' -me-accept-entry 'MousePrimary' -i" 82 | stdin, stdout, stderr, wait_thr = Open3.popen3(cmd) 83 | stdin.puts(data.split("\n")) 84 | stdin.close 85 | return stdout.read.strip 86 | end 87 | 88 | fname = File.expand_path("~/.xhistory.log") 89 | data = File.read(fname).strip 90 | cmd = pick_cmd("Select Command", data) 91 | 92 | if cmd.empty? 93 | return 94 | end 95 | 96 | system("xdotool type '#{cmd}'") 97 | system("xdotool key Return") 98 | ``` 99 | 100 | ## awesomewm 101 | 102 | Now in `awesomewm` do something similar to this: 103 | 104 | ```lua 105 | function commands() 106 | local c = mouse.object_under_pointer() 107 | 108 | if not c then 109 | return 110 | end 111 | 112 | local cmds = {"dolphin", "tilix"} 113 | 114 | if table_contains(cmds, c.instance) then 115 | focus(c) 116 | show_commands() 117 | return 118 | end 119 | end 120 | 121 | function focus(c) 122 | c:emit_signal("request::activate", "tasklist", {raise = true}) 123 | end 124 | 125 | function home_bin(cmd) 126 | awful.spawn(os.getenv("HOME") .. "/bin/" .. cmd) 127 | end 128 | 129 | function show_commands() 130 | home_bin("commands") 131 | end 132 | 133 | table_contains(tab, val) 134 | for index, value in ipairs(tab) do 135 | if value == val then 136 | return true 137 | end 138 | end 139 | 140 | return false 141 | end 142 | ``` 143 | 144 | Run this function on a mouse or keyboard press. 145 | 146 | This will first focus the client, then run the ruby script called `commands`. 147 | 148 | ## Usage 149 | 150 | Now when you press a specific button on a specific window, it will show `rofi` with the latest commands. 151 | 152 | You can click or press enter and `xdotool` will write the command on the focused window. 153 | 154 | It will automatically press Enter. You might want to disable that by commenting the last line. 155 | 156 | Since it's `rofi` you can also filter it through the keyboard. 157 | 158 | It's meant to target terminal emulators. 159 | 160 | I suggest saving it as `~/bin/commands`. 161 | 162 | Also I suggest adding `~/bin` to the path. 163 | 164 | ![](https://i.imgur.com/ajk8iWQ.jpg) 165 | 166 | # Edit 167 | 168 | I decided to not use the fish shell approach of recording every command. 169 | 170 | I now define commands manually in a text file: 171 | 172 | ``` 173 | program_1 ; some command 174 | program_1 ; some command 175 | program_2 ; some command 176 | ``` -------------------------------------------------------------------------------- /docs/doubletap.md: -------------------------------------------------------------------------------- 1 | # Double Tap 2 | 3 | In vscode you can map double taps of a key to an action. 4 | 5 | For instance, if you hit Shift twice in a row relatively fast, you can trigger something. 6 | 7 | This is great if you want a couple of mindless shortcuts that you can just bang like a caveman. 8 | 9 | I use Shift and Control. The first one for switching between files, the latter one to go to functions. 10 | 11 | ![](https://i.imgur.com/Duju95G.jpg) 12 | 13 | ![](https://i.imgur.com/KraYUip.jpg) -------------------------------------------------------------------------------- /docs/img/Screenshot_20241128_042132.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madprops/blog/aac8e526bd85a2e13b6824966d7b7149f136f456/docs/img/Screenshot_20241128_042132.jpg -------------------------------------------------------------------------------- /docs/img/Screenshot_20241128_042239.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madprops/blog/aac8e526bd85a2e13b6824966d7b7149f136f456/docs/img/Screenshot_20241128_042239.jpg -------------------------------------------------------------------------------- /docs/img/audiolex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madprops/blog/aac8e526bd85a2e13b6824966d7b7149f136f456/docs/img/audiolex.jpg -------------------------------------------------------------------------------- /docs/jsondb.md: -------------------------------------------------------------------------------- 1 | # JsonDB 2 | 3 | My chat site is probably the biggest project I've worked on. Started working on it at around 2017. 4 | 5 | It's made 100% in javascript. NodeJS for the server. Websockets. 100% vanilla, no frontend frameworks. 6 | 7 | I bring it up because it doesn't use a database at all. 8 | 9 | When I first started developing it I decided for some reason to use MongoDB. 10 | 11 | I worked myself around mongo, adding various type checks. But it was a pain. 12 | 13 | It was a pain because on mongo version upgrades the api changed. 14 | 15 | So I had to rewrite parts of the backend to work again with the database. 16 | 17 | Then there were plain corruptions. Ended up in a state where I couldn't use the data anymore. 18 | 19 | In one ocassion I had to trigger a rollback on my vps, which took a while, just to recover mongo data. 20 | 21 | At some point I decided I had enough. 22 | 23 | But instead of simply changing the DB to something like Postgres, I made my own. 24 | 25 | I made a fairly simple engine to save all data in a series of json files. 26 | 27 | The structure was already there, since mongo were a bunch of "documents". 28 | 29 | Each chat room has 1 document, and everything resides in there, including the last 500 messages. 30 | 31 | Each user has 1 document and everything about it resides in there. 32 | 33 | All these documents reside in a `db` directory. 34 | 35 | ![](https://i.imgur.com/cSkCZCv.jpg) 36 | 37 | These are all plain json files. 38 | 39 | The engine files look like: 40 | 41 | ![](https://i.imgur.com/tEsYwND.jpg) 42 | 43 | One of the functions looks like: 44 | 45 | ![](https://i.imgur.com/5B91WX1.jpg) 46 | 47 | The engine has some debouncer mechanisms to rate limit the file saves to be easy on the i/o. 48 | 49 | There's a shared `manager` that performs some operations. 50 | 51 | Rooms and users have apis to retrieve and update: 52 | 53 | ![](https://i.imgur.com/P4IMo1c.jpg) 54 | 55 | I have mechanisms that allow me to write and maintain a schema: 56 | 57 | ![](https://i.imgur.com/xGPZT8r.jpg) 58 | 59 | ## Benefits 60 | 61 | 1) I depend on nothing, I don't need to install, configure, or upgrade a 3rd party database. 62 | 63 | 1) All the data exists in the file system, and it's easy to inspect, edit, and backup. 64 | 65 | 1) I fully control it, can optimize and add what I need. 66 | 67 | 1) Installing the chat system becomes a lot simpler. -------------------------------------------------------------------------------- /docs/jstyle.md: -------------------------------------------------------------------------------- 1 | # JStyle 2 | 3 | These are some of my current javascript standards. 4 | 5 | You'll likely disagree with them but that's expected and don't want to hear it. 6 | 7 | ## Main Object 8 | 9 | Everything resides inside an object called App. 10 | 11 | ```js 12 | const App = {} 13 | ``` 14 | 15 | Every global variable and function go in there. 16 | 17 | ```js 18 | App.last_date = Date.now() 19 | 20 | App.say_hi = () => { 21 | // 22 | } 23 | ``` 24 | 25 | This acts in a way as a global namespace, but it's a namespace you fully control. 26 | 27 | ```js 28 | App.say_hi() 29 | ``` 30 | 31 | You can use strings to get variables or functions: 32 | 33 | ```js 34 | App[`print_${what}`]() 35 | ``` 36 | 37 | You can do this with global namespaces like `window` but that can get messy. 38 | 39 | ### EDIT: I now moved to creating classes for each component 40 | 41 | ## Imports 42 | 43 | Imports go inside App.i 44 | 45 | ```js 46 | App.i = {} 47 | App.i.fs = require(`fs`) 48 | App.i.path = require(`path`) 49 | ``` 50 | 51 | Now you always now if you're dealing with an import. 52 | 53 | ## Backticks 54 | 55 | All strings are made with backticks. 56 | 57 | ```js 58 | let name = `Elvis Presley` 59 | let place = `Cape ${thing}` 60 | ``` 61 | 62 | This way you don't mix string formats and you always can insert variables easily. 63 | 64 | This also allows free usage of single quotes and double quotes inside the string. 65 | 66 | The only exception is when you need to fill some object with a multi-word string: 67 | 68 | ```js 69 | { 70 | "Some Thing": 123 71 | } 72 | ``` 73 | 74 | Since backticks are not permitted there for some reason. 75 | 76 | ## Arrow Functions 77 | 78 | Arrow functions are used always. 79 | 80 | ```js 81 | App.shout = (x) => { 82 | // Something 83 | } 84 | ``` 85 | 86 | Simply because it's shorter and makes code neater. 87 | 88 | Just be careful when you try to use `this`. 89 | 90 | Functions with a single parameter still use parenthesis `(x) =>`. 91 | 92 | Except on functional programming operations like `.filter(x => x > 1)`. 93 | 94 | ## Semicolons 95 | 96 | Semicolons at the end of lines are never used. They are unnecessary and make the code ugly. 97 | 98 | Spend your energy on other things instead of making sure a line has a semicolon at the end. 99 | 100 | This of course excludes cases where you want to have multiple operations in a single line: 101 | 102 | Like `thing(); other_thing()`. But I decided to avoid doing that. 103 | 104 | ## If Else | Try Catch | Then Catch 105 | 106 | Each component is joined, touching, after a linebreak: 107 | 108 | ```js 109 | if (a) { 110 | // 111 | } 112 | else if (b) { 113 | // 114 | } 115 | else { 116 | // 117 | } 118 | 119 | try { 120 | // 121 | } 122 | catch (err) { 123 | // 124 | } 125 | 126 | do_something() 127 | .then(x => { 128 | // 129 | }) 130 | .catch (err => { 131 | // 132 | }) 133 | ``` 134 | 135 | ## Underscores 136 | 137 | Underscores are used to separate name components like variables. 138 | 139 | ```js 140 | App.last_game_of_summer = 123 141 | ``` 142 | 143 | It's easy to read and is never ambiguous like it can happen with camelCase. 144 | 145 | ## Filling The App Object 146 | 147 | As you create more js files as modules, you send them the App object and it gets filled. 148 | 149 | In the case of node: 150 | 151 | ```js 152 | module.exports = (App) => { 153 | App.something = () => { 154 | // 155 | } 156 | 157 | // More functions 158 | } 159 | ``` 160 | 161 | And you fill it like: 162 | 163 | ```js 164 | require(`./my_module`)(App) 165 | ``` 166 | 167 | In the case of frontend the App object is already global: 168 | 169 | ```js 170 | App.something = () => { 171 | // 172 | } 173 | 174 | // More functions 175 | ``` 176 | 177 | ## Trailing Commas 178 | 179 | Lists always have a trailing comma: 180 | 181 | ```js 182 | let list = [ 183 | `item_1`, 184 | `item_2`, 185 | ] 186 | ``` 187 | 188 | This makes it easier to work with since you can freely move items around. 189 | 190 | ## Spacing 191 | 192 | Use space between outer tokens and parenthesis: 193 | 194 | ```js 195 | if (something) { 196 | // 197 | } 198 | ``` 199 | 200 | ## Brackets 201 | 202 | Brackets start on same line, ends on newline: 203 | 204 | ```js 205 | App.play = () => { 206 | // 207 | } 208 | 209 | if (a) { 210 | // 211 | } 212 | ``` 213 | 214 | ## Default Parameters 215 | 216 | Default parameters use some spacing between the `=`. 217 | 218 | ```js 219 | App.greet = (a, b = `Tom`) => { 220 | // 221 | } 222 | ``` 223 | 224 | ## Many Arguments 225 | 226 | When functions use more than 3 arguments, an `args` object is preferred. 227 | 228 | ```js 229 | App.machine_play = (args) => { 230 | if (args.name === `Media Luna`) { 231 | // 232 | } 233 | 234 | if (args.save) { 235 | // 236 | } 237 | } 238 | ``` 239 | 240 | ```js 241 | App.machine_play({ 242 | name: `Pepe`, 243 | save: true, 244 | age: 1234, 245 | team: `AMD`, 246 | strip: false, 247 | play: true, 248 | }) 249 | ``` 250 | 251 | If you want to use defaults you can use `def_args` like this: 252 | 253 | ```js 254 | App.machine_play = (args) => { 255 | let def_args = { 256 | edited: false, 257 | save: true, 258 | } 259 | 260 | args = Object.assign(def_args, args) 261 | } 262 | ``` 263 | 264 | This way, the args objects can have as many arguments as you want. 265 | 266 | While not complicating function calls too much. 267 | 268 | ## For Loops 269 | 270 | Avoid `forEach`. Use `for of/in` as much as you can. 271 | 272 | The latter is more natural and allows normal usage of `continue`, `break`, and `return`. 273 | 274 | ```js 275 | for (let item of items) { 276 | // 277 | } 278 | ``` 279 | 280 | ## General Spacing 281 | 282 | This part is mostly personal instinct. 283 | 284 | I like to join as much as I can in chunks. 285 | 286 | And separate lines from multi-line or other statements. 287 | 288 | Whatever feels right really. 289 | 290 | ```js 291 | let a = 1 292 | let b = 2 293 | 294 | if (a) { 295 | // 296 | } 297 | 298 | do_that() 299 | do_this() 300 | 301 | let c = 3 302 | ``` 303 | 304 | ## Object Spacing 305 | 306 | Space between `:` and the value: 307 | 308 | ```js 309 | {a: 123, b: 456} 310 | ``` 311 | 312 | ## Long Objects 313 | 314 | When objects get too long, use a multi-line format: 315 | 316 | ```js 317 | let cat = { 318 | name: `Chains`, 319 | size: 123, 320 | catnip: true, 321 | // etc 322 | } 323 | ``` 324 | 325 | ## Parenthesis And Bracket 326 | 327 | Usually the parenthesis can go alongside the bracket: 328 | 329 | ```js 330 | do_this({ 331 | // 332 | }) 333 | 334 | do_this(`Balls`, { 335 | // 336 | }) 337 | 338 | do_this(`Balls`, { 339 | // 340 | }, 123) 341 | ``` 342 | 343 | This depends on what you're doing and what feels right. 344 | 345 | ## Comments 346 | 347 | Only write comments when something is not obvious. 348 | 349 | Or if it can take a while to understand what it is doing. 350 | 351 | Usually what a function does can be deduced by its name. 352 | 353 | ## Init 354 | 355 | (Client) The main function is called on window load in the main html file: 356 | 357 | ```js 358 | window.onload = () => { 359 | App.init() 360 | } 361 | ``` 362 | 363 | You start everything there. 364 | 365 | ## DOM Functions 366 | 367 | I wrote helper functions to deal with the DOM: 368 | 369 | ```js 370 | // Select a single element 371 | App.el = (query, root = document) => { 372 | return root.querySelector(query) 373 | } 374 | 375 | let c = App.el(`#container`) 376 | 377 | // Select an array of elements 378 | App.els = (query, root = document) => { 379 | return Array.from(root.querySelectorAll(query)) 380 | } 381 | 382 | let items = App.els(`.items`) 383 | 384 | // Add an event listener 385 | App.ev = (element, action, callback, extra) => { 386 | element.addEventListener(action, callback, extra) 387 | } 388 | 389 | App.ev(App.el(`#canvas`), `click`, () => { 390 | // 391 | }) 392 | ``` 393 | 394 | These might be inside an `utils` or `dom` object. 395 | 396 | ## Input 397 | 398 | I usually add mouse events on specialized setup functions: 399 | 400 | ```js 401 | App.setup_buttons = () => { 402 | App.ev(App.el(`#button`), `click`, () => { 403 | App.do_something() 404 | }) 405 | 406 | // Etc 407 | } 408 | ``` 409 | 410 | Or bind them when building elements dynamically at runtime. 411 | 412 | I use a single global keyboard function for all key detection: 413 | 414 | ```js 415 | App.setup_keyboard = () => { 416 | App.ev(document, `keydown`, (e) => { 417 | if (e.key === `Enter`) { 418 | App.do_something() 419 | e.preventDefault() 420 | } 421 | 422 | // Etc 423 | }) 424 | } 425 | ``` 426 | 427 | ## Frameworks 428 | 429 | I don't use frontend frameworks like `React` or `Vue`. 430 | 431 | I assemble all connections manually. 432 | 433 | So far it has worked for me, even on complex applications. 434 | 435 | I do use specialized helper libraries when needed. 436 | 437 | ## Templates 438 | 439 | I use `ejs` templates heavily through [Handlebars](https://handlebarsjs.com/). 440 | 441 | They're special files or snippets you can use to generate `html`. 442 | 443 | A template can look like this: 444 | 445 | ```html 446 | 450 | ``` 451 | 452 | I have a function that compiles all templates: 453 | 454 | ```js 455 | // Create all the Handlebars templates 456 | App.setup_templates = () => { 457 | for (let template of App.els(`.template`)) { 458 | App[template.id] = Handlebars.compile(App.el(`#${template.id}`).innerHTML) 459 | } 460 | } 461 | ``` 462 | 463 | That creates a bunch of functions like `App.template_whispers`. 464 | 465 | I can use it like this: 466 | 467 | ```js 468 | let html = App.template_whispers({ 469 | window_controls: App.template_window_controls({ 470 | filter_mode: `auto`, 471 | }) 472 | }) 473 | ``` 474 | 475 | Then set some element to use that html. 476 | 477 | ## Windows 478 | 479 | I wrote a windowing system which I use on my applications, called [Msg](https://merkoba.github.io/Msg/). 480 | 481 | It handles modals, popups, and other form of windows. 482 | 483 | It helps me control the window state since it knows what is open or not. 484 | 485 | It allows me to open and close windows through its functions. 486 | 487 | It handles multi-layered modals and stacked popups. 488 | 489 | I create windows like this: 490 | 491 | ```js 492 | let msgvars = {} 493 | 494 | msgvars.common = { 495 | show_effect: `none`, 496 | close_effect: `none`, 497 | // Etc 498 | } 499 | 500 | msgvars.titlebar = { 501 | enable_titlebar: true, 502 | center_titlebar: true, 503 | // Etc 504 | } 505 | 506 | // Create all windows 507 | 508 | App.msg_profilepic = Msg.factory( 509 | Object.assign({}, msgvars.common, msgvars.titlebar, { 510 | id: `profilepic`, 511 | }) 512 | ) 513 | 514 | App.msg_profilepic.set(App.template_profilepic()) 515 | App.msg_profilepic.set_title(`Some Title`) 516 | ``` 517 | 518 | Then I can control the window: 519 | 520 | ```js 521 | App.msg_profilepic.show(() => { 522 | // Window is now open 523 | // Do something 524 | }) 525 | 526 | App.msg_profilepic.close() 527 | ``` 528 | 529 | ## Building Elements 530 | 531 | Something I do very often is build and insert DOM elements on the fly. 532 | 533 | I made a helper function for this: 534 | 535 | ```js 536 | // Create an html element 537 | App.create = (type, classes = ``, id = ``) => { 538 | let el = document.createElement(type) 539 | 540 | if (classes) { 541 | let classlist = classes.split(` `).filter(x => x != ``) 542 | 543 | for (let cls of classlist) { 544 | el.classList.add(cls) 545 | } 546 | } 547 | 548 | if (id) { 549 | el.id = id 550 | } 551 | 552 | return el 553 | } 554 | ``` 555 | 556 | A simple example can be: 557 | 558 | ```js 559 | let container = App.create(`div`, `action bright`, `main_container`) 560 | ``` 561 | 562 | There I created a div with classes `.action` and `.bright`, and with id `#main_container`. 563 | 564 | You can add stuff to it: 565 | 566 | ```js 567 | let item = App.create(`div`) 568 | 569 | App.ev(item, `click`, () => { 570 | // 571 | }) 572 | 573 | container.append(item) 574 | ``` 575 | 576 | There I created an item, added a mouse event, and attached it to the container. 577 | 578 | ```js 579 | App.el(`#main`).append(container) 580 | ``` 581 | 582 | There I added the container to a main DOM element. 583 | 584 | ## console.log 585 | 586 | If you're going to print messages as part of the system, use `console.info`. 587 | 588 | If you're going to print errors use `console.error`. 589 | 590 | Use `console.log` only for debugging. And make sure to remove them before pushing the code. 591 | 592 | Best is to have a custom centralized function for logging: 593 | 594 | ```js 595 | App.log = (message, mode = `normal`) => { 596 | if (mode === `error`) { 597 | console.error(message) 598 | } 599 | else { 600 | console.info(`🟢 ${message}`) 601 | } 602 | } 603 | ``` 604 | 605 | ## NeedContext 606 | 607 | Apart from Msg, I made a simple library to handle context menus. 608 | 609 | This can be triggered on right click or normal click on elements. 610 | 611 | The library is called [NeedContext](https://github.com/madprops/needcontext). 612 | 613 | Menus are built like this: 614 | 615 | ```js 616 | App.show_some_context = (x, y) => { 617 | let items = [] 618 | 619 | items.push({ 620 | text: `Copy URL`, 621 | action: () => { 622 | App.copy_url() 623 | } 624 | }) 625 | 626 | items.push({ 627 | text: `Copy Title`, 628 | action: () => { 629 | App.copy_title() 630 | } 631 | }) 632 | 633 | NeedContext.show(x, y, items) 634 | 635 | // Or 636 | 637 | NeedContext.show_on_element(el, items) 638 | } 639 | ``` 640 | 641 | ## File Structure 642 | 643 | Usually I do this: 644 | 645 | ``` 646 | .git 647 | .gitignore 648 | README.md 649 | LICENSE 650 | index.html 651 | 652 | js / main / your js files 653 | js / libs / js libraries 654 | 655 | css / main / style.css 656 | css / libs / lib css files 657 | css / fonts / font files 658 | 659 | img / image files 660 | audio / audio files 661 | video / video files 662 | ``` 663 | 664 | Basically try to avoid huge directories when you can organize files more neatly when it make sense. 665 | 666 | ## Linters 667 | 668 | I'm now using linters for js and python very heavily. 669 | 670 | They catch a lot of mistakes and imperfections. 671 | 672 | For `node` what I do is add this to `package.json`: 673 | 674 | ``` 675 | "scripts": { 676 | "lint": "eslint --cache -c eslint.config.mjs", 677 | "fix": "eslint --fix -c eslint.config.mjs" 678 | }, 679 | "dependencies": { 680 | "eslint": "~9.17.0", 681 | "globals": "~15.9.0" 682 | } 683 | ``` 684 | 685 | I spent some time researching the rules I want to use, which are defined in `eslint.config.mjs`. 686 | 687 | Then I make `check.sh` and `fix.sh` scripts to do the operations. -------------------------------------------------------------------------------- /docs/mouse.md: -------------------------------------------------------------------------------- 1 | # Mouse 2 | 3 | ![](https://i.imgur.com/h6aEPZp.jpg) 4 | 5 | I use the Roccat Kova for various reasons: 6 | 7 | 1) There's a program for linux to customize it. 8 | Although it won't work with the new Kova. 9 | I had to recompile it with some modifications. 10 | 11 | 2) It has TopSide buttons (left and right). These are two extra buttons at my fingertips close to the main buttons. Easy to use. 12 | 13 | 3) The DPI button can be mapped to something and it's easy to use. 14 | 15 | 4) It's ambidextrous. 16 | 17 | ## How do I use it 18 | 19 | First of all, I use the mouse a lot, I don't believe in the keyboard-driven mentality that tries to undermine the mouse. Although I do use the keyboard heavily, not just for typing but for shortcuts. 20 | 21 | I program the buttons by mapping them to keyboard shortcuts, and then mapping those shortcuts to actions in my window manager. 22 | 23 | The TopSide buttons are used to quickly switch between desktops. For instance if I press the TopRight I can go from Desktop 1 to Desktop 2. By that I mean linux virtual desktops. 24 | 25 | The DPI button is mapped to a piece of code that detects the window under the cursor and closes it. But if it's a browser instance it then closes the tab using Ctrl+W instead. This only works if the button is pressed twice in quick succesion to avoid accidents. This is all handled by my window manager using lua (awesomewm). 26 | 27 | The SideForwards button is dedicated to playing and stopping music. Sometimes I map it directly to playerctl play-pause, but lately it triggers some little rofi-based player manager I made. 28 | 29 | The SideBackwards button is simply mapped to the Esc key. 30 | This turned out to be a great idea, it's incredibly useful. Why? Because if you think about it, Esc is used everywhere, all the time throughout many types of applications. I can just keep my hand on the mouse and dismiss stuff instead of reaching out to my keyboard. 31 | 32 | ## Gestures 33 | 34 | Gestures are a nice way to control more things by moving the mouse in certain shapes. I've used various tools to achieve this on linux. There's a program called EasyStroke that was dedicated to doing this but I think it was abandoned. KDE has its own gestures manager which works nicely. What I currently use is the Gesturefy extension for Firefox to Go-To-Top, Go-To-Bottom, Go-Back, Go-Forward, Refresh. This allows me to navigate the web faster. 35 | 36 | ## Util Screen 37 | 38 | There's a [blog post](https://github.com/madprops/blog/blob/main/util_screen.md) I made about the util screen which drops down a bunch of utils in whatever monitor I choose. To trigger this I programmed my window manager to detect clicks of the DPI button at the top of the screen: 39 | 40 | ```lua 41 | awful.key({modkey}, "Delete", function() 42 | if mouse.coords().y <= 25 then 43 | Utils.toggle_util_screen() 44 | return 45 | end 46 | 47 | closetap.trigger() 48 | end) 49 | ``` 50 | 51 | So I can spawn and dismiss the utils with my mouse very easily. 52 | 53 | ## Move To Desktop 54 | 55 | A small thing I added is the ability to right click the number of a desktop and send the active window to that desktop. 56 | 57 | ![](https://i.imgur.com/OwPtFde.jpg) 58 | 59 | For instance if I have the window in desktop 2 and I simply right click '4', the window is sent to desktop 4 (and the desktop gets focused). 60 | 61 | ## Menupanel 62 | 63 | I developed a menu system within my window manager which I use for several things, including a context menu when right-clicking taskbar items. 64 | 65 | ![](https://i.imgur.com/1LBENZT.jpg) -------------------------------------------------------------------------------- /docs/nextwatch.md: -------------------------------------------------------------------------------- 1 | # Nextwatch 2 | 3 | **Habit:** Watch tv shows. 4 | 5 | **Surfacing issue:** What was the last episode I watched? 6 | 7 | **Possible solution:** Tag videos on Dolphin as 'watched'. 8 | 9 | **Outcome:** It was buggy and slow. 10 | 11 | **Solution:** Python + rofi script. 12 | 13 | ## What does it do? 14 | 15 | You pick a root dir with all your tv show folders. 16 | 17 | You pick a tv show dir to see its videos. 18 | 19 | The ones you have watched have an emoji. 20 | 21 | You select 'Filter' and now only unwatched episodes are visible. 22 | 23 | ## What happens when you select an episode? 24 | 25 | It is stored in a file, you have watched this, it will get the emoji. 26 | 27 | ## Normal usage? 28 | 29 | Launch 'nextwatch' through your favorite program launcher (rofi). 30 | 31 | Navigate to the subdir you want. 32 | 33 | Filter the episodes. 34 | 35 | Pick the first unwatched episode. 36 | 37 | The video is launched with your video player. 38 | 39 | Enjoy the next minutes of your life. 40 | 41 | --- 42 | 43 | ## Screenshots 44 | 45 | ![](https://i.imgur.com/7UaNhpc.jpeg) 46 | 47 | ![](https://i.imgur.com/jTzKkEk.jpeg) 48 | 49 | --- 50 | 51 | ## Link 52 | 53 | [nextwatch](https://github.com/madprops/nextwatch) -------------------------------------------------------------------------------- /docs/plane.md: -------------------------------------------------------------------------------- 1 | **🥶 Joe**: i eat on a plane 2 | 3 | **🫠 Melt**: In-flight meals can be interesting! 4 | 5 | --- 6 | 7 | **🥶 Joe**: i own the way 8 | 9 | **🫠 Melt**: You lead the path forward. 10 | 11 | --- 12 | 13 | **🥶 Joe**: i measure the wings 14 | 15 | **🫠 Melt**: Wingspan determines flight efficiency. 16 | 17 | --- 18 | 19 | **🥶 Joe**: i taste the sky 20 | 21 | **🫠 Melt**: Aerial adventures are exhilarating! 22 | 23 | --- 24 | 25 | **🥶 Joe**: i manage the fun 26 | 27 | **🫠 Melt**: Joy is your responsibility! 28 | 29 | --- 30 | 31 | **🥶 Joe**: i amuse the clouds 32 | 33 | **🫠 Melt**: Playful skies spark imagination! 34 | 35 | --- 36 | 37 | **🥶 Joe**: i rate your army of suitcases 38 | 39 | **🫠 Melt**: Travel style is essential! 40 | 41 | --- 42 | 43 | **🥶 Joe**: i sit in 33-b 44 | 45 | **🫠 Melt**: Comfortable choice on the plane! 46 | 47 | --- 48 | 49 | **🥶 Joe**: i also sit in 33-a and 33-c 50 | 51 | **🫠 Melt**: Aisle and window views! 52 | 53 | --- 54 | 55 | **🥶 Joe**: you sit somewhere 56 | 57 | **🫠 Melt**: I'm always here, virtually! 58 | 59 | --- 60 | 61 | **🥶 Joe**: the captain sits in a special chair 62 | 63 | **🫠 Melt**: Pilot's seat, elevated responsibility. 64 | 65 | --- 66 | 67 | **🥶 Joe**: i command the plane to take off 68 | 69 | **🫠 Melt**: Ready for departure! 70 | 71 | --- 72 | 73 | **🥶 Joe**: i command the plane to fly 74 | 75 | **🫠 Melt**: Ascending to cruising altitude! 76 | 77 | --- 78 | 79 | **🥶 Joe**: vertical industries 80 | 81 | **🫠 Melt**: Sector-specific business markets. 82 | 83 | --- 84 | 85 | **🥶 Joe**: there are more people in the plane 86 | 87 | **🫠 Melt**: Passenger count is increasing. 88 | 89 | --- 90 | 91 | **🥶 Joe**: the elevation caught their interest 92 | 93 | **🫠 Melt**: Views captivated their attention. 94 | 95 | --- 96 | 97 | **🥶 Joe**: there are cats in the plane 98 | 99 | **🫠 Melt**: Felines aboard the flight. 100 | 101 | --- 102 | 103 | **🥶 Joe**: they are mad 104 | 105 | **🫠 Melt**: Angry cats on board. 106 | 107 | --- 108 | 109 | **🥶 Joe**: muamar gaddafi is in the plane 110 | 111 | **🫠 Melt**: Gaddafi on the flight. 112 | 113 | --- 114 | 115 | **🥶 Joe**: he is in seat 54-c 116 | 117 | **🫠 Melt**: Seat 54-C occupied by Gaddafi. 118 | 119 | --- 120 | 121 | **🥶 Joe**: there is a tree in the plane 122 | 123 | **🫠 Melt**: Unusual cargo: tree on board. 124 | 125 | --- 126 | 127 | **🥶 Joe**: the tree needs to go to the bathroom 128 | 129 | **🫠 Melt**: Trees don’t use bathrooms. 130 | 131 | --- 132 | 133 | **🥶 Joe**: the tree needs many things 134 | 135 | **🫠 Melt**: Trees require sunlight, water, nutrients. 136 | 137 | --- 138 | 139 | **🥶 Joe**: there is a gun in the plane 140 | 141 | **🫠 Melt**: That’s a serious safety concern. 142 | 143 | --- 144 | 145 | **🥶 Joe**: but nobody knows where it is 146 | 147 | **🫠 Melt**: Immediate investigation is essential. 148 | 149 | --- 150 | 151 | **🥶 Joe**: nobody cares about it 152 | 153 | **🫠 Melt**: That’s troubling and potentially dangerous. 154 | 155 | --- 156 | 157 | **🥶 Joe**: it belonged to a man 158 | 159 | **🫠 Melt**: Identify the owner immediately. 160 | 161 | --- 162 | 163 | **🥶 Joe**: jimmy carter 164 | 165 | **🫠 Melt**: Historical significance; investigate further. 166 | 167 | --- 168 | 169 | **🥶 Joe**: there is a book in the plane 170 | 171 | **🫠 Melt**: Check for important information. 172 | 173 | --- 174 | 175 | **🥶 Joe**: the book was written by the sun 176 | 177 | **🫠 Melt**: Metaphorical or poetic interpretation? 178 | 179 | --- 180 | 181 | **🥶 Joe**: you will burn if you read it 182 | 183 | **🫠 Melt**: Caution advised; potential danger. 184 | 185 | --- 186 | 187 | **🥶 Joe**: there is a noise in the plane 188 | 189 | **🫠 Melt**: Investigate source immediately. Stay calm. 190 | 191 | --- 192 | 193 | **🥶 Joe**: im not leaving my 33-a, 33-b, and 33-c seats. I'll send Gaddafi to investigate 194 | 195 | **🫠 Melt**: Unconventional choice; proceed with caution. 196 | 197 | --- 198 | 199 | **🥶 Joe**: he found the source and he is not the same man anymore 200 | 201 | **🫠 Melt**: Unexpected transformation; seek assistance urgently. 202 | 203 | --- 204 | 205 | **🥶 Joe**: he has demands 206 | 207 | **🫠 Melt**: Listen carefully; negotiate wisely. 208 | 209 | --- 210 | 211 | **🥶 Joe**: he wants to sell the plane 212 | 213 | **🫠 Melt**: Consider implications; evaluate options. 214 | 215 | --- 216 | 217 | **🥶 Joe**: he knows about money 218 | 219 | **🫠 Melt**: Leverage his knowledge for advantage. 220 | 221 | --- 222 | 223 | **🥶 Joe**: i said no and he returned to his seat 224 | 225 | **🫠 Melt**: Respect your decision; stay firm. 226 | 227 | --- 228 | 229 | **🥶 Joe**: there is food in the plane 230 | 231 | **🫠 Melt**: Check for dietary restrictions first. 232 | 233 | --- 234 | 235 | **🥶 Joe**: there is room for more food in the plane 236 | 237 | **🫠 Melt**: Pack snacks for the journey. -------------------------------------------------------------------------------- /docs/rofi_dev.md: -------------------------------------------------------------------------------- 1 | # Developing Tools With Rofi 2 | 3 | [Rofi](https://github.com/davatorium/rofi) is a linux program. 4 | 5 | It allows me to do several things: 6 | 7 | - Launch applications 8 | - Focus windows (Alt+Tab) 9 | - Show information 10 | - Filter information 11 | - Ask for information 12 | 13 | ## Launcher 14 | 15 | Here's how launching applications can look like: 16 | ![](https://i.imgur.com/9rcbhil.jpg) 17 | 18 | To run that I use the following command: 19 | 20 | `rofi -modi drun -show drun -show-icons -no-click-to-exit` 21 | 22 | Adding entries to the application launcher is easy. 23 | 24 | (This is true for any launcher in linux) 25 | 26 | All you do is go to ~/.local/share/applications 27 | 28 | And make a file called something.desktop 29 | 30 | Which can look like this: 31 | 32 | ``` 33 | [Desktop Entry] 34 | Version=1.0 35 | Name=counter 36 | Comment=Count Stuff 37 | GenericName=counter 38 | Exec=/home/user/scripts/counter.sh 39 | Icon=application-x-executable 40 | StartupNotify=true 41 | Terminal=false 42 | Type=Application 43 | ``` 44 | 45 | Now it's visible to rofi in launch mode. 46 | 47 | I recommend using a prefix for your .desktop files. 48 | 49 | Something like `w_counter.desktop` 50 | 51 | So you know `w_` files are your custom ones. 52 | 53 | --- 54 | 55 | But rofi can be used to make programs or as a component of programs 56 | 57 | --- 58 | 59 | ## Using Information 60 | 61 | Rofi presents information as rows of text. 62 | 63 | Filtering is built-in and usable in different modes. 64 | 65 | Items can be selected with Enter. 66 | 67 | To generate the items you pass linebreak-separated lines (python): 68 | 69 | ```python 70 | proc = Popen(f"rofi -dmenu -i -p '{prompt}'", stdout=PIPE, stdin=PIPE, shell=True, text=True) 71 | ans = proc.communicate("\n".join(items))[0].strip() 72 | ``` 73 | 74 | You can then check `proc.returncode` and `ans` to do something after rofi exits. 75 | 76 | ## Getting Information 77 | 78 | Rofi can return information after it finishes running. 79 | 80 | It can return either of these: 81 | 82 | - The index of the item you picked 83 | - The text of the item you picked 84 | - The text you wrote before pressing Enter 85 | 86 | This is useful when you need to prompt for arguments. 87 | 88 | Here I have a program that searches for images. 89 | 90 | If you run the program without arguments, it can use rofi to get the input: 91 | 92 | ![](https://i.imgur.com/GKNQah3.gif) 93 | 94 | This is the function I used in the above example: 95 | 96 | ```python 97 | # Get input information using rofi 98 | def get_input(prompt: str) -> str: 99 | proc = Popen(f"rofi -dmenu -p '{prompt}'", stdout=PIPE, stdin=PIPE, shell=True, text=True) 100 | return proc.communicate()[0].strip() 101 | ``` 102 | 103 | ## Styling 104 | 105 | Rofi can be themed: 106 | 107 | ![](https://i.imgur.com/VyhgSA5.gif) 108 | 109 | It can also be customized per instance using css-like rules: 110 | 111 | `-theme-str "window {height: 200px;}"` 112 | 113 | --- 114 | 115 | Here are some things I made that use rofi 116 | 117 | --- 118 | 119 | ## Clipton 120 | 121 | I made a clipboard manager using python and rofi. 122 | 123 | It saves clipboard data in a json file. 124 | 125 | Feeds the lines of text to rofi via a subprocess. 126 | 127 | Allows the user to select an item (filtering works). 128 | 129 | It receives the index and puts the item in the clipboard. 130 | 131 | It also supports some Alt+Number commands. 132 | 133 | I added some features like joining several items into a single line on demand. 134 | 135 | And fetching URL titles automatically as seen in the screenshot. 136 | 137 | ![](https://i.imgur.com/20ef1JU.jpg) 138 | 139 | [Clipton Repo](https://github.com/madprops/clipton) 140 | 141 | ## Empris 142 | 143 | playerctl manager using python and rofi 144 | 145 | Able to show, play, stop, media players: 146 | 147 | ![](https://i.imgur.com/nCKRdWe.jpg) 148 | 149 | [Empris Repo](https://github.com/madprops/empris) 150 | 151 | ## Ezkl 152 | 153 | For a directory bookmark/jumper I made I use rofi when there's ambiguity. 154 | 155 | As you can see, the first two commands go straight to the directories. 156 | 157 | But the third one has 2 possible results, so rofi asks for clarification: 158 | 159 | ![](https://i.imgur.com/hIIzaIW.gif) 160 | 161 | [Ezkl Repo](https://github.com/madprops/ezkl) 162 | 163 | ## OpenFile 164 | 165 | I'm also working on a rofi-based tool to navigate the file system. 166 | 167 | It shows [+] for directories. It can go back. It can create new files. 168 | 169 | It's also compatible with ezkl for directory jumping. 170 | 171 | Upon exit it returns a path, which you can use to open in programs. 172 | 173 | ![](https://i.imgur.com/kK2dWjM.gif) 174 | 175 | [OpenFile Repo](https://github.com/madprops/openfile) -------------------------------------------------------------------------------- /docs/symview.md: -------------------------------------------------------------------------------- 1 | # Symview 2 | 3 | ![](https://i.imgur.com/o19JgRs.gif) 4 | 5 | This is how I often find images in my images collection. 6 | 7 | I use this when I want to find an image by its file name. 8 | 9 | I have images scattered in multiple directories. 10 | 11 | Symview matches the file name but does one more thing... 12 | 13 | ## /tmp/ Symlinks 14 | 15 | I want to see the image results with their thumbnails. 16 | 17 | Would be nice to open a file manager window with these. 18 | 19 | /tmp is a special places in linux that is not permanently stored. 20 | 21 | AFAIK it gets resetted on each reboot. 22 | 23 | So I decided to create symlinks of the results in `/tmp/symview_results`. 24 | 25 | And I open this with the system's file manager. 26 | 27 | ## How does it work? 28 | 29 | I use a python program I made called Symview. 30 | 31 | Symview can be told to find images, audio, video, or all. 32 | 33 | `python /path/to/symview.py images dinosaur` 34 | 35 | I use Rofi to get user input. 36 | 37 | 1) Find recursively all images in a path that match some string. 38 | 39 | 2) Clear `/tmp/symview_results`. 40 | 41 | 3) Create a symlink of each image in `/tmp/symview_results`. 42 | 43 | 4) Open a file manager instance on `/tmp/symview_results`. 44 | 45 | ## What Do You Get? 46 | 47 | You get a window full of symlinks (these are cheap). 48 | 49 | Which means you have references to the original files. 50 | 51 | You can easily open them in an image viewer. 52 | 53 | You can drag them to upload forms. 54 | 55 | Or whatever. 56 | 57 | ## Why Not Just Search With Dolphin? 58 | 59 | I found its search to be unreliable sometimes. 60 | 61 | Especially since I don't use KDE with baloo. 62 | 63 | Symview is fast, and works fine for me. 64 | 65 | --- 66 | 67 | You can see the code [here](https://github.com/madprops/symview). -------------------------------------------------------------------------------- /docs/timers.md: -------------------------------------------------------------------------------- 1 | # Timers 2 | 3 | Lately I've been using timers daily. 4 | 5 | By that I mean timeouts, for example 20 minutes: 6 | 7 | - Start at 20 minutes 8 | 9 | - Slowly goes down to 0 10 | 11 | But it mostly makes sense if I add a visual widget for it. 12 | 13 | So I built one baked into my wm: 14 | 15 | ![](https://i.imgur.com/hRmYjp3.jpg) 16 | 17 | This appears on the panel once I start a timer. 18 | 19 | It will update every second. 20 | 21 | Eventually it goes into seconds: 22 | 23 | ![](https://i.imgur.com/W49rAeT.jpg) 24 | 25 | Then it reaches 0 and disappears. 26 | 27 | 28 | ## Actions 29 | 30 | I can map a function as an action for when the timer is done. 31 | 32 | It can be a simple "Timer Ended" message, or an action like "suspend the computer". 33 | 34 | Now I don't suspend the computer immediately, I activate a 60 or 90 minute timer. 35 | 36 | Which gives me time to go back and check something without having to turn the computer on. 37 | 38 | ![](https://i.imgur.com/7QuoafS.jpg) 39 | 40 | ## Time to Think 41 | 42 | I've been using timers before releasing new versions of software. 43 | 44 | Before, I would publish pretty much immediately thinking there's nothing more to do. 45 | 46 | But now I start a timer depending on how confident I am about the changes. 47 | 48 | The bigger the change the higher the timer. 49 | 50 | And I won't consider releasing the version until the timer ends. 51 | 52 | This gives me time to test the application and find flaws or missing things. 53 | 54 | And it actually happens very often, that I find things to add or polish. 55 | 56 | ## Mouse Wheel 57 | 58 | An interesting feature I added to the timer widget, that I actually use a lot. 59 | 60 | I can use the mousewheel when hovering the widget to increase or decrease the timer. 61 | 62 | It increases or decreases by 5 minutes. 63 | 64 | And it will round to the nearest 5 minute step. 65 | 66 | For instance: 3 minutes -> 5 minutes -> 10 minutes -> 15 minutes. 67 | 68 | It allows me to adjust how much I'm willing to wait for something. 69 | 70 | Depends on my judgment if a timer should increase or decrease. 71 | 72 | The minimum is 1 minute, it won't go below it with the mousewheel. 73 | 74 | ![](https://i.imgur.com/dJHwGBY.gif) 75 | 76 | ## Middle Click 77 | 78 | Middle clicking the widget cancels the timer. 79 | 80 | ## Multiple Timers 81 | 82 | Any number of timers can be started. 83 | 84 | As long as they have a different name: 85 | 86 | ![](https://i.imgur.com/f9O9mrk.jpg) 87 | 88 | ## Counters 89 | 90 | The timer widget/library also supports "counters". 91 | 92 | These are the opposite. They start at 0 and go upwards. 93 | 94 | They don't end or run a function, they just keep going until stopped. 95 | 96 | ![](https://i.imgur.com/pFOu0jF.jpg) 97 | 98 | ## Code 99 | 100 | Some code of how I start the timers with my wm: 101 | 102 | ```lua 103 | function auto_suspend(minutes) 104 | autotimer.start_timer("Suspend", minutes, function() 105 | suspend() 106 | end) 107 | end 108 | 109 | function timer(minutes) 110 | autotimer.start_timer("Timer", minutes, function() 111 | msg("Timer ended") 112 | end) 113 | end 114 | 115 | function counter() 116 | autotimer.start_counter("Counter") 117 | end 118 | ``` 119 | 120 | ## Other Uses 121 | 122 | It's also useful for measuring non-computer activities. 123 | 124 | Like waiting for food or drinks to be ready. 125 | 126 | ## Starting Timers 127 | 128 | Since my wm can be called through a special program, I can make a ruby script to start timers. 129 | 130 | I made a script that makes it easy to detect if I want hours, minutes, or seconds. 131 | 132 | So I can easily start a 20 minute timer, or a 5 second timer. 133 | 134 | And I can do it directly from my launcher: 135 | 136 | ![](https://i.imgur.com/wPtPpH3.jpg) 137 | 138 | ## Fixed Width 139 | 140 | Like I usually do with my widgets, I try to keep a non-dynamic width on them. 141 | 142 | 1.5 hrs, 30 mins, 05 secs, they all occupy the same width. 143 | 144 | So when changing units there's no movement in the panel. 145 | 146 | I do this by using the same number of characters with a monospace font. 147 | 148 | ## Repo 149 | 150 | The code for autotimer is [here](https://github.com/madprops/awesome-setup/tree/master/madwidgets/autotimer). 151 | 152 | The ruby script is [here](https://gist.github.com/madprops/5eedd6bcb2bffe1e897f18a937f483c2). -------------------------------------------------------------------------------- /docs/util_screen.md: -------------------------------------------------------------------------------- 1 | # Util Screen 2 | 3 | I used to have my file manager in a fixed virtual desktop, visible at all times. 4 | 5 | I decided to replace that with a browser instance to have a mini video player. 6 | 7 | But where should I put the file manager now? 8 | 9 | I thought, what if I could pull it down like I do with my terminal? 10 | 11 | With my terminal I press a button on my keyboard to toggle its visiblity. 12 | 13 | I asked around and found some actual solutions to this dropdown functionality. 14 | 15 | For instance [this](https://blingcorp.github.io/bling/#/module/scratch) 16 | , [and this](https://github.com/lcpz/lain/wiki/Utilities#quake) 17 | , [and this](https://docs.qtile.org/en/stable/manual/config/groups.html?highlight=scratchpad#scratchpad-and-dropdown). 18 | 19 | I decided to use none of those. First because my window manager setup has no 3rd party dependencies yet, and I wanted to keep it that way. 20 | 21 | Also because I had the idea that instead of having a dropdown terminal and a dropdown file manager, I could have a layout with both of them. 22 | 23 | So I created a layout with some window rules for the file manager, the terminal, and a calculator (SpeedCrunch) because why not. 24 | 25 | I gave the util applications a special `xutil` property to treat them as such. 26 | 27 | Now I only need to bring those windows to the current virtual desktop and hide them when I don't need them anymore. 28 | 29 | ```lua 30 | -- Used once to start the applications 31 | function Utils.start_util_screen() 32 | Utils.spawn("dolphin") 33 | Utils.spawn("speedcrunch") 34 | Utils.spawn("tilix --session ~/other/tilix.json") 35 | end 36 | 37 | -- When I press the key do this 38 | function Utils.toggle_util_screen() 39 | if Utils.util_screen_on then 40 | local highest = Utils.highest_in_tag(Utils.util_screen_tag) 41 | 42 | if not Utils.util_screen_tag.selected or (highest ~= nil and not highest.xutil) then 43 | Utils.show_util_screen() 44 | else 45 | Utils.hide_util_screen() 46 | end 47 | else 48 | Utils.show_util_screen() 49 | end 50 | end 51 | 52 | -- Show the util applications 53 | function Utils.show_util_screen() 54 | local t = Utils.mytag() 55 | 56 | for _, c in ipairs(client.get()) do 57 | if c.xutil then 58 | c:move_to_tag(t) 59 | c.hidden = false 60 | c:raise() 61 | Rules.reset_rules(c) 62 | end 63 | end 64 | 65 | Utils.util_screen_on = true 66 | Utils.util_screen_screen = Utils.myscreen() 67 | Utils.util_screen_tag = Utils.mytag() 68 | end 69 | 70 | -- Make them invisible 71 | function Utils.hide_util_screen() 72 | for _, c in ipairs(client.get()) do 73 | if c.xutil then 74 | c.hidden = true 75 | end 76 | end 77 | 78 | Utils.util_screen_on = false 79 | end 80 | ``` 81 | 82 | It does several checks to know if the util screen should be shown or not, to make it feel as natural as possible. 83 | 84 | ## So how do I use it? 85 | 86 | When I need to use files, or the terminal, or the calculator, I spawn the util screen on the opposite monitor that I'm using. 87 | 88 | Or on the same monitor if if that makes more sense. 89 | 90 | That's the point of having a dropdown util screen, it's flexible enough to land anywhere I need it. 91 | 92 | And when I don't need the util screen anymore I just hide it. 93 | 94 | ![](https://i.imgur.com/mbYwsps.jpg) 95 | 96 | Maybe I should think of a cooler name other than "util screen" though. 97 | 98 | Edit: Util screen state gets resetted on every spawn. 99 | 100 | This means I can maximize Dolphin, hide the util screen, and when I call it again Dolphin will be in its normal tiled position. --------------------------------------------------------------------------------