├── 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 | 
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 | 
40 |
41 | ---
42 |
43 | Checked its multi-language capabilities
44 |
45 | 
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 | 
60 |
61 | ---
62 |
63 | We found it was really easy to change the personality.
64 |
65 | All it needs are simple instructions.
66 |
67 | 
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 | 
77 |
78 | 
79 |
80 | 
81 |
82 | Here's The Undertaker:
83 |
84 | 
85 |
86 | ---
87 |
88 | Currently it has these commands:
89 |
90 | 
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 | 
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 | 
--------------------------------------------------------------------------------
/docs/audiolex.md:
--------------------------------------------------------------------------------
1 | 
--------------------------------------------------------------------------------
/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 | 
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 | 
12 |
13 | 
--------------------------------------------------------------------------------
/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 | 
36 |
37 | These are all plain json files.
38 |
39 | The engine files look like:
40 |
41 | 
42 |
43 | One of the functions looks like:
44 |
45 | 
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 | 
54 |
55 | I have mechanisms that allow me to write and maintain a schema:
56 |
57 | 
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 | 
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 | 
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 | 
--------------------------------------------------------------------------------
/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 | 
46 |
47 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
174 |
175 | [OpenFile Repo](https://github.com/madprops/openfile)
--------------------------------------------------------------------------------
/docs/symview.md:
--------------------------------------------------------------------------------
1 | # Symview
2 |
3 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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.
--------------------------------------------------------------------------------