mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2025-05-21 21:05:50 -07:00
update
This commit is contained in:
parent
7be98945bb
commit
0a9415b058
184
.config/cava/config
Normal file
184
.config/cava/config
Normal file
@ -0,0 +1,184 @@
|
||||
## Configuration file for CAVA. Default values are commented out. Use either ';' or '#' for commenting.
|
||||
|
||||
|
||||
[general]
|
||||
|
||||
# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0
|
||||
mode = 'scientific'
|
||||
|
||||
# Accepts only non-negative values.
|
||||
framerate = 144
|
||||
|
||||
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
|
||||
# new as of 0.6.0 autosens of low values (dynamic range)
|
||||
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0
|
||||
; autosens = 1
|
||||
; overshoot = 0
|
||||
|
||||
# Manual sensitivity in %. Autosens must be turned off for this to take effect.
|
||||
# 200 means double height. Accepts only non-negative values.
|
||||
sensitivity = 100
|
||||
|
||||
# The number of bars (0-200). 0 sets it to auto (fill up console).
|
||||
# Bars' width and space between bars in number of characters.
|
||||
bars = 0
|
||||
bar_width = 2
|
||||
bar_spacing = 0
|
||||
|
||||
|
||||
# Lower and higher cutoff frequencies for lowest and highest bars
|
||||
# the bandwidth of the visualizer.
|
||||
# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
|
||||
# Cava will automatically increase the higher cutoff if a too low band is specified.
|
||||
; lower_cutoff_freq = 50
|
||||
; higher_cutoff_freq = 10000
|
||||
|
||||
|
||||
# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and
|
||||
# only check for input once per second. Cava will wake up once input is detected. 0 = disable.
|
||||
; sleep_timer = 0
|
||||
|
||||
|
||||
[input]
|
||||
|
||||
# Audio capturing method. Possible methods are: 'pulse', 'alsa', 'fifo', 'sndio' or 'shmem'
|
||||
# Defaults to 'pulse', 'alsa' or 'fifo', in that order, dependent on what support cava was built with.
|
||||
#
|
||||
# All input methods uses the same config variable 'source'
|
||||
# to define where it should get the audio.
|
||||
#
|
||||
# For pulseaudio 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
|
||||
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
|
||||
#
|
||||
# For alsa 'source' will be the capture device.
|
||||
# For fifo 'source' will be the path to fifo-file.
|
||||
# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address
|
||||
|
||||
method = pipewire
|
||||
source = auto
|
||||
|
||||
; method = alsa
|
||||
; source = hw:Loopback,1
|
||||
|
||||
; method = fifo
|
||||
; source = /tmp/mpd.fifo
|
||||
; sample_rate = 44100
|
||||
; sample_bits = 16
|
||||
|
||||
; method = shmem
|
||||
; source = /squeezelite-AA:BB:CC:DD:EE:FF
|
||||
|
||||
; method = portaudio
|
||||
; source = auto
|
||||
|
||||
|
||||
[output]
|
||||
|
||||
# Output method. Can be 'ncurses', 'noncurses' or 'raw'.
|
||||
# 'noncurses' uses a custom framebuffer technique and draws only changes
|
||||
# from frame to frame. 'ncurses' is default if supported
|
||||
#
|
||||
# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data
|
||||
# stream of the bar heights that can be used to send to other applications.
|
||||
# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.
|
||||
method = ncurses
|
||||
|
||||
# Visual channels. Can be 'stereo' or 'mono'.
|
||||
# 'stereo' mirrors both channels with low frequencies in center.
|
||||
# 'mono' outputs left to right lowest to highest frequencies.
|
||||
# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.
|
||||
; channels = stereo
|
||||
; mono_option = average
|
||||
|
||||
# Raw output target. A fifo will be created if target does not exist.
|
||||
; raw_target = /dev/stdout
|
||||
|
||||
# Raw data format. Can be 'binary' or 'ascii'.
|
||||
data_format = ascii
|
||||
|
||||
# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
|
||||
bit_format = 16bit
|
||||
|
||||
# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
|
||||
ascii_max_range = 1000
|
||||
|
||||
# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
|
||||
# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
|
||||
bar_delimiter = 59
|
||||
frame_delimiter = 10
|
||||
|
||||
|
||||
|
||||
[color]
|
||||
|
||||
# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
|
||||
# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires
|
||||
# ncurses output method and a terminal that can change color definitions such as Gnome-terminal or rxvt.
|
||||
# if supported, ncurses mode will be forced on if user defined colors are used.
|
||||
# default is to keep current terminal color
|
||||
; foreground = blue
|
||||
foreground = red
|
||||
|
||||
# Gradient mode, only hex defined colors (and thereby ncurses mode) are supported,
|
||||
# background must also be defined in hex or remain commented out. 1 = on, 0 = off.
|
||||
# You can define as many as 8 different colors. They range from bottom to top of screen
|
||||
gradient = 1
|
||||
gradient_count = 8
|
||||
|
||||
# macchiato w/background
|
||||
background = '#24273A'
|
||||
gradient_color_1 = '#8bd5ca'
|
||||
gradient_color_2 = '#91d7e3'
|
||||
gradient_color_3 = '#7dc4e4'
|
||||
gradient_color_4 = '#8aadf4'
|
||||
gradient_color_5 = '#c6a0f6'
|
||||
gradient_color_6 = '#f5bde6'
|
||||
gradient_color_7 = '#ee99a0'
|
||||
gradient_color_8 = '#ed8796'
|
||||
|
||||
# macchiato rainbow
|
||||
# background = '#24273A'
|
||||
# gradient_color_1 = '#ED8796'
|
||||
# gradient_color_2 = '#F5A97F'
|
||||
# gradient_color_3 = '#EED49F'
|
||||
# gradient_color_4 = '#A6DA95'
|
||||
# gradient_color_5 = '#7DC4E4'
|
||||
# gradient_color_6 = '#8AADF4'
|
||||
# gradient_color_7 = '#B7BDF8'
|
||||
# gradient_color_8 = '#C6A0F6'
|
||||
|
||||
|
||||
[smoothing]
|
||||
|
||||
# Percentage value for integral smoothing. Takes values from 0 - 100.
|
||||
# Higher values means smoother, but less precise. 0 to disable.
|
||||
integral = 0
|
||||
|
||||
# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable.
|
||||
; monstercat = 1
|
||||
; waves = 1
|
||||
|
||||
# Set gravity percentage for "drop off". Higher values means bars will drop faster.
|
||||
# Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable "drop off".
|
||||
; gravity = 100
|
||||
|
||||
|
||||
# In bar height, bars that would have been lower that this will not be drawn.
|
||||
; ignore = 0
|
||||
|
||||
|
||||
[eq]
|
||||
|
||||
# This one is tricky. You can have as much keys as you want.
|
||||
# Remember to uncomment more then one key! More keys = more precision.
|
||||
# Look at readme.md on github for further explanations and examples.
|
||||
; 1 = 1 # bass
|
||||
; 2 = 1
|
||||
; 3 = 1 # midtone
|
||||
; 4 = 1
|
||||
; 5 = 1 # treble
|
||||
1=2
|
||||
2=2
|
||||
3=1
|
||||
4=1
|
||||
5=0.6
|
165
.config/cava/config.bak
Normal file
165
.config/cava/config.bak
Normal file
@ -0,0 +1,165 @@
|
||||
## Configuration file for CAVA. Default values are commented out. Use either ';' or '#' for commenting.
|
||||
|
||||
|
||||
[general]
|
||||
|
||||
# Smoothing mode. Can be 'normal', 'scientific' or 'waves'. DEPRECATED as of 0.6.0
|
||||
; mode = normal
|
||||
|
||||
# Accepts only non-negative values.
|
||||
; framerate = 60
|
||||
|
||||
# 'autosens' will attempt to decrease sensitivity if the bars peak. 1 = on, 0 = off
|
||||
# new as of 0.6.0 autosens of low values (dynamic range)
|
||||
# 'overshoot' allows bars to overshoot (in % of terminal height) without initiating autosens. DEPRECATED as of 0.6.0
|
||||
; autosens = 1
|
||||
; overshoot = 20
|
||||
|
||||
# Manual sensitivity in %. If autosens is enabled, this will only be the initial value.
|
||||
# 200 means double height. Accepts only non-negative values.
|
||||
; sensitivity = 100
|
||||
|
||||
# The number of bars (0-200). 0 sets it to auto (fill up console).
|
||||
# Bars' width and space between bars in number of characters.
|
||||
; bars = 0
|
||||
; bar_width = 2
|
||||
; bar_spacing = 1
|
||||
|
||||
|
||||
# Lower and higher cutoff frequencies for lowest and highest bars
|
||||
# the bandwidth of the visualizer.
|
||||
# Note: there is a minimum total bandwidth of 43Mhz x number of bars.
|
||||
# Cava will automatically increase the higher cutoff if a too low band is specified.
|
||||
; lower_cutoff_freq = 50
|
||||
; higher_cutoff_freq = 10000
|
||||
|
||||
|
||||
# Seconds with no input before cava goes to sleep mode. Cava will not perform FFT or drawing and
|
||||
# only check for input once per second. Cava will wake up once input is detected. 0 = disable.
|
||||
; sleep_timer = 0
|
||||
|
||||
|
||||
[input]
|
||||
|
||||
# Audio capturing method. Possible methods are: 'pulse', 'alsa', 'fifo', 'sndio' or 'shmem'
|
||||
# Defaults to 'pulse', 'alsa' or 'fifo', in that order, dependent on what support cava was built with.
|
||||
#
|
||||
# All input methods uses the same config variable 'source'
|
||||
# to define where it should get the audio.
|
||||
#
|
||||
# For pulseaudio 'source' will be the source. Default: 'auto', which uses the monitor source of the default sink
|
||||
# (all pulseaudio sinks(outputs) have 'monitor' sources(inputs) associated with them).
|
||||
#
|
||||
# For alsa 'source' will be the capture device.
|
||||
# For fifo 'source' will be the path to fifo-file.
|
||||
# For shmem 'source' will be /squeezelite-AA:BB:CC:DD:EE:FF where 'AA:BB:CC:DD:EE:FF' will be squeezelite's MAC address
|
||||
; method = pulse
|
||||
; source = auto
|
||||
|
||||
; method = alsa
|
||||
; source = hw:Loopback,1
|
||||
|
||||
; method = fifo
|
||||
; source = /tmp/mpd.fifo
|
||||
; sample_rate = 44100
|
||||
; sample_bits = 16
|
||||
|
||||
; method = shmem
|
||||
; source = /squeezelite-AA:BB:CC:DD:EE:FF
|
||||
|
||||
; method = portaudio
|
||||
; source = auto
|
||||
|
||||
|
||||
[output]
|
||||
|
||||
# Output method. Can be 'ncurses', 'noncurses' or 'raw'.
|
||||
# 'noncurses' uses a custom framebuffer technique and draws only changes
|
||||
# from frame to frame. 'ncurses' is default if supported
|
||||
#
|
||||
# 'raw' is an 8 or 16 bit (configurable via the 'bit_format' option) data
|
||||
# stream of the bar heights that can be used to send to other applications.
|
||||
# 'raw' defaults to 200 bars, which can be adjusted in the 'bars' option above.
|
||||
; method = ncurses
|
||||
|
||||
# Visual channels. Can be 'stereo' or 'mono'.
|
||||
# 'stereo' mirrors both channels with low frequencies in center.
|
||||
# 'mono' outputs left to right lowest to highest frequencies.
|
||||
# 'mono_option' set mono to either take input from 'left', 'right' or 'average'.
|
||||
; channels = stereo
|
||||
; mono_option = average
|
||||
|
||||
# Raw output target. A fifo will be created if target does not exist.
|
||||
; raw_target = /dev/stdout
|
||||
|
||||
# Raw data format. Can be 'binary' or 'ascii'.
|
||||
; data_format = binary
|
||||
|
||||
# Binary bit format, can be '8bit' (0-255) or '16bit' (0-65530).
|
||||
; bit_format = 16bit
|
||||
|
||||
# Ascii max value. In 'ascii' mode range will run from 0 to value specified here
|
||||
; ascii_max_range = 1000
|
||||
|
||||
# Ascii delimiters. In ascii format each bar and frame is separated by a delimiters.
|
||||
# Use decimal value in ascii table (i.e. 59 = ';' and 10 = '\n' (line feed)).
|
||||
; bar_delimiter = 59
|
||||
; frame_delimiter = 10
|
||||
|
||||
|
||||
|
||||
[color]
|
||||
|
||||
# Colors can be one of seven predefined: black, blue, cyan, green, magenta, red, white, yellow.
|
||||
# Or defined by hex code '#xxxxxx' (hex code must be within ''). User defined colors requires
|
||||
# ncurses output method and a terminal that can change color definitions such as Gnome-terminal or rxvt.
|
||||
# if supported, ncurses mode will be forced on if user defined colors are used.
|
||||
# default is to keep current terminal color
|
||||
; background = default
|
||||
; foreground = default
|
||||
|
||||
# Gradient mode, only hex defined colors (and thereby ncurses mode) are supported,
|
||||
# background must also be defined in hex or remain commented out. 1 = on, 0 = off.
|
||||
# You can define as many as 8 different colors. They range from bottom to top of screen
|
||||
; gradient = 0
|
||||
; gradient_count = 8
|
||||
; gradient_color_1 = '#59cc33'
|
||||
; gradient_color_2 = '#80cc33'
|
||||
; gradient_color_3 = '#a6cc33'
|
||||
; gradient_color_4 = '#cccc33'
|
||||
; gradient_color_5 = '#cca633'
|
||||
; gradient_color_6 = '#cc8033'
|
||||
; gradient_color_7 = '#cc5933'
|
||||
; gradient_color_8 = '#cc3333'
|
||||
|
||||
|
||||
|
||||
[smoothing]
|
||||
|
||||
# Percentage value for integral smoothing. Takes values from 0 - 100.
|
||||
# Higher values means smoother, but less precise. 0 to disable.
|
||||
; integral = 77
|
||||
|
||||
# Disables or enables the so-called "Monstercat smoothing" with or without "waves". Set to 0 to disable.
|
||||
; monstercat = 0
|
||||
; waves = 0
|
||||
|
||||
# Set gravity percentage for "drop off". Higher values means bars will drop faster.
|
||||
# Accepts only non-negative values. 50 means half gravity, 200 means double. Set to 0 to disable "drop off".
|
||||
; gravity = 100
|
||||
|
||||
|
||||
# In bar height, bars that would have been lower that this will not be drawn.
|
||||
; ignore = 0
|
||||
|
||||
|
||||
[eq]
|
||||
|
||||
# This one is tricky. You can have as much keys as you want.
|
||||
# Remember to uncomment more then one key! More keys = more precision.
|
||||
# Look at readme.md on github for further explanations and examples.
|
||||
; 1 = 1 # bass
|
||||
; 2 = 1
|
||||
; 3 = 1 # midtone
|
||||
; 4 = 1
|
||||
; 5 = 1 # treble
|
79
.config/cava/shaders/bar_spectrum.frag
Normal file
79
.config/cava/shaders/bar_spectrum.frag
Normal file
@ -0,0 +1,79 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
// bar values. defaults to left channels first (low to high), then right (high to low).
|
||||
uniform float bars[512];
|
||||
|
||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
||||
uniform int bar_width; // bar width (configurable), not used here
|
||||
uniform int bar_spacing; // space bewteen bars (configurable)
|
||||
|
||||
uniform vec3 u_resolution; // window resolution
|
||||
|
||||
//colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
|
||||
uniform vec3 bg_color; // background color
|
||||
uniform vec3 fg_color; // foreground color
|
||||
|
||||
uniform int gradient_count;
|
||||
uniform vec3 gradient_colors[8]; // gradient colors
|
||||
|
||||
vec3 normalize_C(float y,vec3 col_1, vec3 col_2, float y_min, float y_max)
|
||||
{
|
||||
//create color based on fraction of this color and next color
|
||||
float yr = (y - y_min) / (y_max - y_min);
|
||||
return col_1 * (1.0 - yr) + col_2 * yr;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// find which bar to use based on where we are on the x axis
|
||||
float x = u_resolution.x * fragCoord.x;
|
||||
int bar = int(bars_count * fragCoord.x);
|
||||
|
||||
//calculate a bar size
|
||||
float bar_size = u_resolution.x / bars_count;
|
||||
|
||||
//the y coordinate and bar values are the same
|
||||
float y = bars[bar];
|
||||
|
||||
// make sure there is a thin line at bottom
|
||||
if (y * u_resolution.y < 1.0)
|
||||
{
|
||||
y = 1.0 / u_resolution.y;
|
||||
}
|
||||
|
||||
//draw the bar up to current height
|
||||
if (y > fragCoord.y)
|
||||
{
|
||||
//make some space between bars basen on settings
|
||||
if (x > (bar + 1) * (bar_size) - bar_spacing)
|
||||
{
|
||||
fragColor = vec4(bg_color,1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gradient_count == 0)
|
||||
{
|
||||
fragColor = vec4(fg_color,1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
//find which color in the configured gradient we are at
|
||||
int color = int((gradient_count - 1) * fragCoord.y);
|
||||
|
||||
//find where on y this and next color is supposed to be
|
||||
float y_min = color / (gradient_count - 1.0);
|
||||
float y_max = (color + 1.0) / (gradient_count - 1.0);
|
||||
|
||||
//make color
|
||||
fragColor = vec4(normalize_C(fragCoord.y, gradient_colors[color], gradient_colors[color + 1], y_min, y_max), 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fragColor = vec4(bg_color,1.0);
|
||||
}
|
||||
}
|
38
.config/cava/shaders/normalized_bars.frag
Normal file
38
.config/cava/shaders/normalized_bars.frag
Normal file
@ -0,0 +1,38 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
// bar values. defaults to left channels first (low to high), then right (high to low).
|
||||
uniform float bars[512];
|
||||
|
||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
||||
|
||||
uniform vec3 u_resolution; // window resolution, not used here
|
||||
|
||||
//colors, configurable in cava config file
|
||||
uniform vec3 bg_color; // background color(r,g,b) (0.0 - 1.0), not used here
|
||||
uniform vec3 fg_color; // foreground color, not used here
|
||||
|
||||
float normalize_C(float x, float x_min, float x_max, float r_min, float r_max )
|
||||
{
|
||||
float xr;
|
||||
xr = (r_max-r_min) * (x - x_min) / (x_max - x_min) + r_min;
|
||||
return xr;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// find which bar to use based on where we are on the x axis
|
||||
int bar = int(bars_count * fragCoord.x);
|
||||
|
||||
// create a normal along the y axis based on the bar height
|
||||
float x = normalize_C(fragCoord.y, 1.0, 0.0, 0.0, bars[bar]);
|
||||
|
||||
// set color
|
||||
fragColor.r=fg_color.x*x;
|
||||
fragColor.g=fg_color.y*x;
|
||||
fragColor.b=fg_color.z*x;
|
||||
fragColor.a=1.0;
|
||||
|
||||
}
|
34
.config/cava/shaders/northern_lights.frag
Normal file
34
.config/cava/shaders/northern_lights.frag
Normal file
@ -0,0 +1,34 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
// bar values. defaults to left channels first (low to high), then right (high to low).
|
||||
uniform float bars[512];
|
||||
|
||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
||||
|
||||
uniform vec3 u_resolution; // window resolution, not used here
|
||||
|
||||
//colors, configurable in cava config file
|
||||
uniform vec3 bg_color; // background color(r,g,b) (0.0 - 1.0), not used here
|
||||
uniform vec3 fg_color; // foreground color, not used here
|
||||
|
||||
void main()
|
||||
{
|
||||
// find which bar to use based on where we are on the x axis
|
||||
int bar = int(bars_count * fragCoord.x);
|
||||
|
||||
float bar_y = 1.0 - abs((fragCoord.y - 0.5)) * 2.0;
|
||||
float y = (bars[bar]) * bar_y;
|
||||
|
||||
float bar_x = (fragCoord.x - float(bar) / float(bars_count)) * bars_count;
|
||||
float bar_r = 1.0 - abs((bar_x - 0.5)) * 2;
|
||||
|
||||
bar_r = bar_r * bar_r * 2;
|
||||
|
||||
// set color
|
||||
fragColor.r = fg_color.x * y * bar_r;
|
||||
fragColor.g = fg_color.y * y * bar_r;
|
||||
fragColor.b = fg_color.z * y * bar_r;
|
||||
}
|
14
.config/cava/shaders/pass_through.vert
Normal file
14
.config/cava/shaders/pass_through.vert
Normal file
@ -0,0 +1,14 @@
|
||||
#version 330
|
||||
|
||||
|
||||
// Input vertex data, different for all executions of this shader.
|
||||
layout(location = 0) in vec3 vertexPosition_modelspace;
|
||||
|
||||
// Output data ; will be interpolated for each fragment.
|
||||
out vec2 fragCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = vec4(vertexPosition_modelspace,1);
|
||||
fragCoord = (vertexPosition_modelspace.xy+vec2(1,1))/2.0;
|
||||
}
|
53
.config/cava/shaders/spectrogram.frag
Normal file
53
.config/cava/shaders/spectrogram.frag
Normal file
@ -0,0 +1,53 @@
|
||||
#version 330
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
// bar values. defaults to left channels first (low to high), then right (high
|
||||
// to low).
|
||||
uniform float bars[512];
|
||||
|
||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
||||
uniform int bar_width; // bar width (configurable), not used here
|
||||
uniform int bar_spacing; // space bewteen bars (configurable)
|
||||
|
||||
uniform vec3 u_resolution; // window resolution
|
||||
|
||||
// colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
|
||||
uniform vec3 bg_color; // background color
|
||||
uniform vec3 fg_color; // foreground color
|
||||
|
||||
uniform int gradient_count;
|
||||
uniform vec3 gradient_colors[8]; // gradient colors
|
||||
|
||||
uniform sampler2D inputTexture; // Texture from the first render pass
|
||||
|
||||
vec3 normalize_C(float y, vec3 col_1, vec3 col_2, float y_min, float y_max) {
|
||||
// create color based on fraction of this color and next color
|
||||
float yr = (y - y_min) / (y_max - y_min);
|
||||
return col_1 * (1.0 - yr) + col_2 * yr;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// find which bar to use based on where we are on the y axis
|
||||
int bar = int(bars_count * fragCoord.y);
|
||||
float y = bars[bar];
|
||||
float band_size = 1.0 / float(bars_count);
|
||||
float current_band_min = bar * band_size;
|
||||
float current_band_max = (bar + 1) * band_size;
|
||||
|
||||
int hist_length = 512;
|
||||
float win_size = 1.0 / hist_length;
|
||||
|
||||
if (fragCoord.x > 1.0 - win_size) {
|
||||
|
||||
if (fragCoord.y > current_band_min && fragCoord.y < current_band_max) {
|
||||
|
||||
fragColor = vec4(fg_color * y, 1.0);
|
||||
}
|
||||
} else {
|
||||
vec2 offsetCoord = fragCoord;
|
||||
offsetCoord.x += float(win_size);
|
||||
fragColor = texture(inputTexture, offsetCoord);
|
||||
}
|
||||
}
|
112
.config/cava/shaders/winamp_line_style_spectrum.frag
Normal file
112
.config/cava/shaders/winamp_line_style_spectrum.frag
Normal file
@ -0,0 +1,112 @@
|
||||
#version 330
|
||||
|
||||
// Emulate the "line style" spectrum analyzer from Winamp 2.
|
||||
// Try this config for a demonstration:
|
||||
|
||||
/*
|
||||
[general]
|
||||
bar_width = 2
|
||||
bar_spacing = 0
|
||||
higher_cutoff_freq = 22000
|
||||
|
||||
[output]
|
||||
method = sdl_glsl
|
||||
channels = mono
|
||||
fragment_shader = winamp_line_style_spectrum.frag
|
||||
|
||||
[color]
|
||||
background = '#000000'
|
||||
gradient = 1
|
||||
gradient_color_1 = '#319C08'
|
||||
gradient_color_2 = '#29CE10'
|
||||
gradient_color_3 = '#BDDE29'
|
||||
gradient_color_4 = '#DEA518'
|
||||
gradient_color_5 = '#D66600'
|
||||
gradient_color_6 = '#CE2910'
|
||||
|
||||
[smoothing]
|
||||
noise_reduction = 10
|
||||
*/
|
||||
|
||||
in vec2 fragCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
// bar values. defaults to left channels first (low to high), then right (high to low).
|
||||
uniform float bars[512];
|
||||
|
||||
uniform int bars_count; // number of bars (left + right) (configurable)
|
||||
uniform int bar_width; // bar width (configurable), not used here
|
||||
uniform int bar_spacing; // space bewteen bars (configurable)
|
||||
|
||||
uniform vec3 u_resolution; // window resolution
|
||||
|
||||
//colors, configurable in cava config file (r,g,b) (0.0 - 1.0)
|
||||
uniform vec3 bg_color; // background color
|
||||
uniform vec3 fg_color; // foreground color
|
||||
|
||||
uniform int gradient_count;
|
||||
uniform vec3 gradient_colors[8]; // gradient colors
|
||||
|
||||
vec3 normalize_C(float y,vec3 col_1, vec3 col_2, float y_min, float y_max)
|
||||
{
|
||||
//create color based on fraction of this color and next color
|
||||
float yr = (y - y_min) / (y_max - y_min);
|
||||
return col_1 * (1.0 - yr) + col_2 * yr;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
// find which bar to use based on where we are on the x axis
|
||||
float x = u_resolution.x * fragCoord.x;
|
||||
int bar = int(bars_count * fragCoord.x);
|
||||
|
||||
//calculate a bar size
|
||||
float bar_size = u_resolution.x / bars_count;
|
||||
|
||||
//the y coordinate is stretched by 4X to resemble Winamp
|
||||
float y = min(bars[bar] * 4.0, 1.0);
|
||||
|
||||
// make sure there is a thin line at bottom
|
||||
if (y * u_resolution.y < 1.0)
|
||||
{
|
||||
y = 1.0 / u_resolution.y;
|
||||
}
|
||||
|
||||
vec4 bar_color;
|
||||
|
||||
if (gradient_count == 0)
|
||||
{
|
||||
bar_color = vec4(fg_color,1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
//find color in the configured gradient for the top of the bar
|
||||
int color = int((gradient_count - 1) * y);
|
||||
|
||||
//find where on y this and next color is supposed to be
|
||||
float y_min = float(color) / (gradient_count - 1.0);
|
||||
float y_max = float(color + 1) / (gradient_count - 1.0);
|
||||
|
||||
//make a solid color for the entire bar
|
||||
bar_color = vec4(normalize_C(y, gradient_colors[color], gradient_colors[color + 1], y_min, y_max), 1.0);
|
||||
}
|
||||
|
||||
|
||||
//draw the bar up to current height
|
||||
if (y > fragCoord.y)
|
||||
{
|
||||
//make some space between bars based on settings
|
||||
if (x > (bar + 1) * (bar_size) - bar_spacing)
|
||||
{
|
||||
fragColor = vec4(bg_color,1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
fragColor = bar_color;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fragColor = vec4(bg_color,1.0);
|
||||
}
|
||||
}
|
@ -100,7 +100,7 @@ bind = $mainMod SHIFT, n, exec, swaync-client -t
|
||||
bind = SUPER, m, exec, ~/.local/bin/mpv-add.sh
|
||||
|
||||
bind = SUPER SHIFT, s, exec, slurp | grim -g - - | wl-copy
|
||||
bind = ,code:107, exec, ~/.local/bin/screenshot.sh
|
||||
bind = ,code:107, exec, ~/.local/bin/screenshot
|
||||
bind = SUPER SHIFT, o, exec, ~/.local/bin/ocr.sh
|
||||
bind = $mainMod, o, exec, ~/.local/bin/ocr.sh
|
||||
|
||||
|
@ -1,124 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from subprocess import Popen
|
||||
from sys import argv
|
||||
from sys import exit as sysexit
|
||||
|
||||
from rofi import Rofi
|
||||
|
||||
# BROWSER = "qutebrowser"
|
||||
# BROWSER = "microsoft-edge-beta"
|
||||
# BROWSER = "google-chrome-stable"
|
||||
BROWSER = "firefox"
|
||||
OPEN_TYPES = ["window", "tab"]
|
||||
|
||||
OPTIONS = [
|
||||
"Anilist - https://anilist.co/home",
|
||||
"Apprise - http://thebox:8888/",
|
||||
"Audiobookshelf - https://audiobookshelf.suda.codes",
|
||||
"Authentik - http://thebox:9000",
|
||||
"Calendar - https://nextcloud.suda.codes/apps/calendar",
|
||||
"Capital One - https://myaccounts.capitalone.com/accountSummary",
|
||||
"Chase Bank - https://secure03ea.chase.com/web/auth/dashboard#/dashboard/overviewAccounts/overview/singleDeposit",
|
||||
"ChatGPT - https://chat.openai.com/chat",
|
||||
"CloudBeaver - https://cloudbeaver.suda.codes",
|
||||
"Cloudflare - https://dash.cloudflare.com/",
|
||||
"CoinMarketCap - https://coinmarketcap.com/watchlist/",
|
||||
"Dashy - http://thebox:4000",
|
||||
"Deemix - http://thebox:3358",
|
||||
"F1TV - https://f1tv.suda.codes",
|
||||
"Fidelity - https://login.fidelity.com/ftgw/Fas/Fidelity/RtlCust/Login/Init?AuthRedUrl=https://oltx.fidelity.com/ftgw/fbc/oftop/portfolio#summary",
|
||||
"Firefly3 - https://firefly.suda.codes",
|
||||
"Gitea - https://gitea.suda.codes",
|
||||
"Github - https://github.com",
|
||||
"Ghostfolio - http://thebox:3334",
|
||||
"Grafana - http://thebox:3333",
|
||||
"Grafana (sudacode) - https://grafana.sudacode.com/d/1lcjN0bik3/nginx-logs-and-geo-map?orgId=1&refresh=10s",
|
||||
"Grafana (suda.codes) - http://thebox:3333/d/1lcjN0bik3/nginx-logs-and-geo-map?orgId=1&refresh=1m",
|
||||
"Grafana (Loki) - http://thebox:3333/explore?schemaVersion=1&panes=%7B%22rtz%22%3A%7B%22datasource%22%3A%22bdvgbaoxphu68c%22%2C%22queries%22%3A%5B%7B%22refId%22%3A%22A%22%2C%22expr%22%3A%22%22%2C%22queryType%22%3A%22range%22%2C%22datasource%22%3A%7B%22type%22%3A%22loki%22%2C%22uid%22%3A%22bdvgbaoxphu68c%22%7D%7D%5D%2C%22range%22%3A%7B%22from%22%3A%22now-1h%22%2C%22to%22%3A%22now%22%7D%7D%7D&orgId=1",
|
||||
"Homepage - https://homepage.suda.codes",
|
||||
"HomeAssistant - http://thebox:8123",
|
||||
"Icloud - https://www.icloud.com/",
|
||||
"Interactive Brokers - https://ndcdyn.interactivebrokers.com/sso/Login?RL=1&locale=en_US",
|
||||
"Jackett - http://thebox:9117",
|
||||
"Jellyseerr - http://thebox:5055",
|
||||
"Jellyfin - http://thebox-ts:8096",
|
||||
"Jellyfin (YouTube) - http://thebox-ts:8097",
|
||||
"Jellyfin (Vue) - http://thebox-ts:8098",
|
||||
"Kanboard - https://kanboard.suda.codes",
|
||||
"Komga - http://thebox:3359",
|
||||
"Lidarr - http://thebox:3357",
|
||||
"Linkding - http://thebox:3341",
|
||||
"Lychee - https://lychee.sudacode.com",
|
||||
"Medusa - https://medusa.suda.codes",
|
||||
"Metamask - https://portfolio.metamask.io/",
|
||||
"MeTube - https://metube.suda.codes",
|
||||
"Netdata - https://netdata.suda.codes",
|
||||
"Navidrome - http://thebox:3346",
|
||||
"Nextcloud - https://nextcloud.suda.codes",
|
||||
"Nzbhydra - http://thebox:5076",
|
||||
"OpenBooks - https://openbooks.suda.codes",
|
||||
"Pihole - http://pi5/admin",
|
||||
"qBittorrent - https://qbit.suda.codes",
|
||||
"Paperless - https://paperless.suda.codes",
|
||||
"Photoprism - http://thebox:2342",
|
||||
"Portainer - http://thebox:10003",
|
||||
"Prometheus - https://prometheus.suda.codes",
|
||||
"Pterodactyl - https://gameserver.suda.codes",
|
||||
"Radarr - https://radarr.suda.codes",
|
||||
"Reddit (Anime) - https://www.reddit.com/r/anime/",
|
||||
"Reddit (Selfhosted) - https://www.reddit.com/r/selfhosted/",
|
||||
"Robinhood - https://robinhood.com",
|
||||
"Sabnzbd - https://sabnzbd.suda.codes",
|
||||
"Sonarr - https://sonarr.suda.codes",
|
||||
"Sonarr Anime - http://thebox:6969",
|
||||
"Skinport - https://skinport.com/",
|
||||
"Steamfolio - https://steamfolio.com/CustomPortfolio",
|
||||
"Sudacode - https://sudacode.com",
|
||||
"Tailscale - https://login.tailscale.com/admin/machines",
|
||||
"Tanoshi - http://thebox:3356",
|
||||
"Tranga - http://thebox:9555",
|
||||
"Tdarr - http://thebox:8265/",
|
||||
"TD Ameritrade - https://invest.ameritrade.com",
|
||||
"ThinkOrSwim - https://trade.thinkorswim.com",
|
||||
"Umami - https://umami.sudacode.com",
|
||||
"Vaultwarden - https://vault.suda.codes",
|
||||
"Wallabag - https://wallabag.suda.codes",
|
||||
"Youtube - https://youtube.com",
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(argv) < 2:
|
||||
print("Usage: rofi-open.py <window_type>")
|
||||
sysexit(1)
|
||||
open_type = argv[1].strip().lower()
|
||||
if open_type not in OPEN_TYPES:
|
||||
print("Invalid open type: {}".format(open_type))
|
||||
print("Valid open types: {}".format(", ".join(OPEN_TYPES)))
|
||||
sysexit(1)
|
||||
try:
|
||||
r = Rofi(
|
||||
config_file="~/.config/rofi/aniwrapper-dracula.rasi",
|
||||
theme_str="configuration {dpi: 144;} window {width: 75%;} listview {columns: 2; lines: 10;}",
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sysexit(1)
|
||||
index, key = r.select("Select link to open", OPTIONS, rofi_args=["-i"])
|
||||
if index < 0 or index >= len(OPTIONS):
|
||||
print("Invalid index:", index)
|
||||
sysexit(1)
|
||||
url = OPTIONS[index].split("-")
|
||||
if isinstance(url, list) and len(url) > 2:
|
||||
url = "-".join(url[1:]).strip()
|
||||
else:
|
||||
url = url[1].strip()
|
||||
print("Opening:", url)
|
||||
"""Open a URL in browser: <BROWSER>."""
|
||||
if open_type == "tab":
|
||||
with Popen([BROWSER, url]) as proc:
|
||||
proc.wait()
|
||||
else:
|
||||
with Popen([BROWSER, "--new-window", url]) as proc:
|
||||
proc.wait()
|
||||
# with Popen([BROWSER, "--target", open_type, url]) as proc:
|
@ -2,42 +2,35 @@
|
||||
"$schema": "/etc/xdg/swaync/configSchema.json",
|
||||
"positionX": "right",
|
||||
"positionY": "top",
|
||||
"layer": "overlay",
|
||||
"control-center-layer": "top",
|
||||
"layer-shell": true,
|
||||
"cssPriority": "application",
|
||||
"control-center-margin-top": 0,
|
||||
"control-center-margin-bottom": 0,
|
||||
"control-center-margin-right": 0,
|
||||
"cssPriority": "user",
|
||||
|
||||
"control-center-width": 380,
|
||||
"control-center-height": 860,
|
||||
"control-center-margin-top": 2,
|
||||
"control-center-margin-bottom": 2,
|
||||
"control-center-margin-right": 1,
|
||||
"control-center-margin-left": 0,
|
||||
"notification-2fa-action": true,
|
||||
"notification-inline-replies": false,
|
||||
"notification-icon-size": 64,
|
||||
"notification-body-image-height": 100,
|
||||
|
||||
"notification-window-width": 400,
|
||||
"notification-icon-size": 48,
|
||||
"notification-body-image-height": 160,
|
||||
"notification-body-image-width": 200,
|
||||
"timeout": 10,
|
||||
"timeout-low": 5,
|
||||
"timeout-critical": 0,
|
||||
"fit-to-screen": true,
|
||||
"relative-timestamps": true,
|
||||
"control-center-width": 500,
|
||||
"control-center-height": 600,
|
||||
"notification-window-width": 500,
|
||||
|
||||
"timeout": 4,
|
||||
"timeout-low": 2,
|
||||
"timeout-critical": 6,
|
||||
|
||||
"fit-to-screen": false,
|
||||
"keyboard-shortcuts": true,
|
||||
"image-visibility": "when-available",
|
||||
"transition-time": 200,
|
||||
"hide-on-clear": false,
|
||||
"hide-on-action": true,
|
||||
"hide-on-action": false,
|
||||
"script-fail-notify": true,
|
||||
"scripts": {
|
||||
"example-script": {
|
||||
"exec": "echo 'Do something...'",
|
||||
"urgency": "Normal"
|
||||
},
|
||||
"example-action-script": {
|
||||
"exec": "echo 'Do something actionable!'",
|
||||
"urgency": "Normal",
|
||||
"run-on": "action"
|
||||
}
|
||||
},
|
||||
"notification-visibility": {
|
||||
@ -48,43 +41,23 @@
|
||||
}
|
||||
},
|
||||
"widgets": [
|
||||
"inhibitors",
|
||||
"mpris",
|
||||
"title",
|
||||
"dnd",
|
||||
"notifications",
|
||||
"mpris",
|
||||
"menubar",
|
||||
"buttons-grid",
|
||||
"volume"
|
||||
"notifications"
|
||||
],
|
||||
"widget-config": {
|
||||
"inhibitors": {
|
||||
"text": "Inhibitors",
|
||||
"button-text": "Clear All",
|
||||
"clear-all-button": true
|
||||
},
|
||||
"title": {
|
||||
"text": "Notifications",
|
||||
"clear-all-button": true,
|
||||
"button-text": "Clear All"
|
||||
"button-text": " "
|
||||
},
|
||||
"dnd": {
|
||||
"text": "Do Not Disturb"
|
||||
"text": "Do not disturb"
|
||||
},
|
||||
"mpris": {
|
||||
"image-size": 96,
|
||||
"image-radius": 12
|
||||
},
|
||||
"buttons-grid": {
|
||||
"actions": [
|
||||
{
|
||||
"label": "直",
|
||||
"type": "toggle",
|
||||
"active": true,
|
||||
"command": "sh -c '[[ $SWAYNC_TOGGLE_STATE == true ]] && nmcli radio wifi on || nmcli radio wifi off'",
|
||||
"update_command": "sh -c '[[ $(nmcli radio wifi) == \"enabled\" ]] && echo true || echo false'"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,622 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "SwayNotificationCenter JSON schema",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
"description": "Pointer to the schema against which this document should be validated."
|
||||
},
|
||||
"positionX": {
|
||||
"type": "string",
|
||||
"description": "Horizontal position of control center and notification window",
|
||||
"default": "right",
|
||||
"enum": ["right", "left", "center"]
|
||||
},
|
||||
"layer": {
|
||||
"type": "string",
|
||||
"description": "Layer of notification window",
|
||||
"default": "overlay",
|
||||
"enum": ["background", "bottom", "top", "overlay"]
|
||||
},
|
||||
"layer-shell": {
|
||||
"type": "boolean",
|
||||
"description": "Wether or not the windows should be opened as layer-shell surfaces. Note: Requires swaync restart to apply",
|
||||
"default": true
|
||||
},
|
||||
"cssPriority": {
|
||||
"type": "string",
|
||||
"description": "Which GTK priority to use when loading the default and user CSS files. Pick \"user\" to override XDG_CONFIG_HOME/gtk-3.0/gtk.css",
|
||||
"default": "application",
|
||||
"enum": ["application", "user"]
|
||||
},
|
||||
"positionY": {
|
||||
"type": "string",
|
||||
"description": "Vertical position of control center and notification window",
|
||||
"default": "top",
|
||||
"enum": ["top", "center", "bottom"]
|
||||
},
|
||||
"control-center-positionX": {
|
||||
"type": "string",
|
||||
"description": "Optional: Horizontal position of the control center. Supersedes positionX if not set to `none`",
|
||||
"default": "none",
|
||||
"enum": ["right", "left", "center", "none"]
|
||||
},
|
||||
"control-center-positionY": {
|
||||
"type": "string",
|
||||
"description": "Optional: Vertical position of the control center. Supersedes positionY if not set to `none`",
|
||||
"default": "none",
|
||||
"enum": ["top", "bottom", "none"]
|
||||
},
|
||||
"control-center-margin-top": {
|
||||
"type": "integer",
|
||||
"description": "The margin (in pixels) at the top of the control center. 0 to disable",
|
||||
"default": 0
|
||||
},
|
||||
"control-center-margin-bottom": {
|
||||
"type": "integer",
|
||||
"description": "The margin (in pixels) at the bottom of the control center. 0 to disable",
|
||||
"default": 0
|
||||
},
|
||||
"control-center-margin-right": {
|
||||
"type": "integer",
|
||||
"description": "The margin (in pixels) at the right of the control center. 0 to disable",
|
||||
"default": 0
|
||||
},
|
||||
"control-center-margin-left": {
|
||||
"type": "integer",
|
||||
"description": "The margin (in pixels) at the left of the control center. 0 to disable",
|
||||
"default": 0
|
||||
},
|
||||
"control-center-layer": {
|
||||
"type": "string",
|
||||
"description": "Layer of control center window",
|
||||
"default": "none",
|
||||
"enum": ["background", "bottom", "top", "overlay", "none"]
|
||||
},
|
||||
"control-center-exclusive-zone": {
|
||||
"type": "boolean",
|
||||
"description": "Whether or not the control center should follow the compositors exclusive zones. An example would be setting it to \"false\" to cover your panel/dock.",
|
||||
"default": true
|
||||
},
|
||||
"notification-2fa-action": {
|
||||
"type": "boolean",
|
||||
"description": "If each notification should display a 'COPY \"1234\"' action",
|
||||
"default": true
|
||||
},
|
||||
"notification-inline-replies": {
|
||||
"type": "boolean",
|
||||
"description": "If notifications should display a text field to reply if the sender requests it. NOTE: Replying in popup notifications is only available if the compositor supports GTK Layer-Shell ON_DEMAND keyboard interactivity.",
|
||||
"default": false
|
||||
},
|
||||
"notification-icon-size": {
|
||||
"type": "integer",
|
||||
"description": "The notification icon size (in pixels). The app icon size is 1/3",
|
||||
"default": 64,
|
||||
"minimum": 16
|
||||
},
|
||||
"notification-body-image-height": {
|
||||
"type": "integer",
|
||||
"description": "The notification body image height (in pixels)",
|
||||
"default": 100,
|
||||
"minimum": 100
|
||||
},
|
||||
"notification-body-image-width": {
|
||||
"type": "integer",
|
||||
"description": "The notification body image width (in pixels)",
|
||||
"default": 200,
|
||||
"minimum": 200
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer",
|
||||
"description": "The notification timeout for notifications with normal priority",
|
||||
"default": 10
|
||||
},
|
||||
"timeout-low": {
|
||||
"type": "integer",
|
||||
"description": "The notification timeout for notifications with low priority",
|
||||
"default": 5
|
||||
},
|
||||
"timeout-critical": {
|
||||
"type": "integer",
|
||||
"description": "The notification timeout for notifications with critical priority. 0 to disable",
|
||||
"default": 0
|
||||
},
|
||||
"notification-window-width": {
|
||||
"type": "integer",
|
||||
"description": "Width of the notification in pixels",
|
||||
"default": 500
|
||||
},
|
||||
"fit-to-screen": {
|
||||
"type": "boolean",
|
||||
"description": "If the control center should expand to both edges of the screen",
|
||||
"default": true
|
||||
},
|
||||
"relative-timestamps": {
|
||||
"type": "boolean",
|
||||
"description": "Display notification timestamps relative to now e.g. \"26 minutes ago\". If false, a local iso8601-formatted absolute timestamp is displayed.",
|
||||
"default": true
|
||||
},
|
||||
"control-center-height": {
|
||||
"type": "integer",
|
||||
"description": "Height of the control center in pixels. Ignored when 'fit-to-screen' is set to 'true'",
|
||||
"default": 600,
|
||||
"minimum": 300
|
||||
},
|
||||
"control-center-width": {
|
||||
"type": "integer",
|
||||
"description": "Width of the control center in pixels",
|
||||
"default": 500,
|
||||
"minimum": 300
|
||||
},
|
||||
"keyboard-shortcuts": {
|
||||
"type": "boolean",
|
||||
"description": "If control center should use keyboard shortcuts",
|
||||
"default": true
|
||||
},
|
||||
"image-visibility": {
|
||||
"type": "string",
|
||||
"description": "The notification image visibility when no icon is available.",
|
||||
"default": "when-available",
|
||||
"enum": ["always", "when-available", "never"]
|
||||
},
|
||||
"transition-time": {
|
||||
"type": "integer",
|
||||
"description": "The notification animation duration. 0 to disable",
|
||||
"default": 200
|
||||
},
|
||||
"hide-on-clear": {
|
||||
"type": "boolean",
|
||||
"description": "Hides the control center after pressing \"Clear All\"",
|
||||
"default": false
|
||||
},
|
||||
"hide-on-action": {
|
||||
"type": "boolean",
|
||||
"description": "Hides the control center when clicking on notification action",
|
||||
"default": true
|
||||
},
|
||||
"script-fail-notify": {
|
||||
"type": "boolean",
|
||||
"description": "Sends a notification if a script fails to run",
|
||||
"default": true
|
||||
},
|
||||
"scripts": {
|
||||
"type": "object",
|
||||
"description": "Which scripts to check and potentially run for every notification. If the notification doesn't include one of the properties, that property will be ignored. All properties (except for exec) use regex. If all properties match the given notification, the script will be run. Only the first matching script will be run.",
|
||||
"minProperties": 1,
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^.{1,}$": {
|
||||
"type": "object",
|
||||
"description": "Your script object.",
|
||||
"required": ["exec"],
|
||||
"minProperties": 2,
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"exec": {
|
||||
"type": "string",
|
||||
"description": "The script to run. Can also run regular shell commands."
|
||||
},
|
||||
"app-name": {
|
||||
"type": "string",
|
||||
"description": "The app-name. Uses Regex."
|
||||
},
|
||||
"desktop-entry": {
|
||||
"type": "string",
|
||||
"description": "The desktop-entry. Uses Regex."
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"description": "The summary of the notification. Uses Regex."
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "The body of the notification. Uses Regex."
|
||||
},
|
||||
"urgency": {
|
||||
"type": "string",
|
||||
"description": "The urgency of the notification.",
|
||||
"default": "Normal",
|
||||
"enum": ["Low", "Normal", "Critical"]
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "Which category the notification belongs to. Uses Regex."
|
||||
},
|
||||
"run-on": {
|
||||
"type": "string",
|
||||
"description": "Whether to run the script on an action being activated, or when the notification is received.",
|
||||
"enum": ["action", "receive"],
|
||||
"default": "receive"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notification-visibility": {
|
||||
"type": "object",
|
||||
"description": "Set the visibility of each incoming notification. If the notification doesn't include one of the properties, that property will be ignored. All properties (except for state) use regex. If all properties match the given notification, the notification will be follow the provided state. Only the first matching object will be used.",
|
||||
"minProperties": 1,
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^.{1,}$": {
|
||||
"type": "object",
|
||||
"description": "Your script object.",
|
||||
"required": ["state"],
|
||||
"minProperties": 2,
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string",
|
||||
"description": "The notification visibility state.",
|
||||
"default": "enabled",
|
||||
"enum": ["ignored", "muted", "enabled", "transient"]
|
||||
},
|
||||
"app-name": {
|
||||
"type": "string",
|
||||
"description": "The app-name. Uses Regex."
|
||||
},
|
||||
"desktop-entry": {
|
||||
"type": "string",
|
||||
"description": "The desktop-entry. Uses Regex."
|
||||
},
|
||||
"summary": {
|
||||
"type": "string",
|
||||
"description": "The summary of the notification. Uses Regex."
|
||||
},
|
||||
"body": {
|
||||
"type": "string",
|
||||
"description": "The body of the notification. Uses Regex."
|
||||
},
|
||||
"urgency": {
|
||||
"type": "string",
|
||||
"description": "The urgency of the notification.",
|
||||
"default": "Normal",
|
||||
"enum": ["Low", "Normal", "Critical"]
|
||||
},
|
||||
"override-urgency": {
|
||||
"type": "string",
|
||||
"description": "The new urgency of the notification (optional)",
|
||||
"default": "unset",
|
||||
"enum": ["unset", "low", "normal", "critical"]
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"description": "Which category the notification belongs to. Uses Regex."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgets": {
|
||||
"type": "array",
|
||||
"description": "Which order and which widgets to display. If the \"notifications\" widget isn't specified, it will be placed at the bottom.",
|
||||
"default": ["inhibitors", "title", "dnd", "notifications"],
|
||||
"items": {
|
||||
"type": "string",
|
||||
"$comment": "Sadly can't use regex and enums at the same time. Fix in the future?",
|
||||
"pattern": "^[a-zA-Z0-9_-]{1,}(#[a-zA-Z0-9_-]{1,}){0,1}?$"
|
||||
}
|
||||
},
|
||||
"widget-config": {
|
||||
"type": "object",
|
||||
"description": "Configure specific widget properties.",
|
||||
"additionalProperties": false,
|
||||
"$comment": "New widgets go here in \"patternProperties\" ↓",
|
||||
"patternProperties": {
|
||||
"^title(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$comment": "References the widget structure from \"widgets\" below",
|
||||
"$ref": "#/widgets/title"
|
||||
},
|
||||
"^dnd(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/dnd"
|
||||
},
|
||||
"^label(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/label"
|
||||
},
|
||||
"^mpris(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/mpris"
|
||||
},
|
||||
"^buttons-grid(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/buttons-grid"
|
||||
},
|
||||
"^menubar(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/menubar"
|
||||
},
|
||||
"^volume(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/volume"
|
||||
},
|
||||
"^backlight(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$ref": "#/widgets/backlight"
|
||||
},
|
||||
"^inhibitors(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"$comment": "References the widget structure from \"widgets\" below",
|
||||
"$ref": "#/widgets/inhibitors"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"widgets": {
|
||||
"$comment": "New widgets go here",
|
||||
"title": {
|
||||
"type": "object",
|
||||
"description": "Control Center Title Widget",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "The title of the widget",
|
||||
"default": "Notifications"
|
||||
},
|
||||
"clear-all-button": {
|
||||
"type": "boolean",
|
||||
"description": "Wether to display a \"Clear All\" button",
|
||||
"default": true
|
||||
},
|
||||
"button-text": {
|
||||
"type": "string",
|
||||
"description": "\"Clear All\" button text",
|
||||
"default": "Clear All"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dnd": {
|
||||
"type": "object",
|
||||
"description": "Control Center Do Not Disturb Widget",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "The title of the widget",
|
||||
"default": "Do Not Disturb"
|
||||
}
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"type": "object",
|
||||
"description": "A generic widget that allows the user to add custom text",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "The text content of the widget",
|
||||
"default": "Label Text"
|
||||
},
|
||||
"max-lines": {
|
||||
"type": "integer",
|
||||
"description": "The maximum lines",
|
||||
"default": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
"mpris": {
|
||||
"type": "object",
|
||||
"description": "A widget that displays multiple music players",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"image-size": {
|
||||
"type": "integer",
|
||||
"description": "The size of the album art",
|
||||
"default": 96
|
||||
},
|
||||
"image-radius": {
|
||||
"type": "integer",
|
||||
"description": "The border radius of the album art. Will be overriden by setting the border-radius in the style.css for the \".widget-mpris-album-art\" class",
|
||||
|
||||
"default": 12
|
||||
},
|
||||
"blur": {
|
||||
"type": "bool",
|
||||
"description": "Appy the artwork as the MPRIS background and blur it",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"buttons-grid": {
|
||||
"type": "object",
|
||||
"description": "A widget to add a grid of buttons that execute shell commands",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"description": "A list of actions containing a label and a command",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Text to be displayed in button",
|
||||
"default": "label"
|
||||
},
|
||||
"command": {
|
||||
"type": "string",
|
||||
"description": "Command to be executed on click",
|
||||
"default": ""
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Type of the button; toggle buttons receive the .active css class and an env variable 'SWAYNC_TOGGLE_STATE' is set. See example in the default config.json",
|
||||
"default": "normal",
|
||||
"enum": ["normal", "toggle"]
|
||||
},
|
||||
"update-command": {
|
||||
"type": "string",
|
||||
"description": "Command to be executed on visibility change of cc to update the active state of the toggle button (should echo true or false)",
|
||||
"default": ""
|
||||
},
|
||||
"active": {
|
||||
"type": "bool",
|
||||
"description": "Wether the toggle button is active as default or not",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"menubar": {
|
||||
"type": "object",
|
||||
"description": "A bar that contains action-buttons and buttons to open a dropdown with action-buttons",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^menu(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"type": "object",
|
||||
"description": "A button that opens a dropdown with action-buttons",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Text to be displayed in button",
|
||||
"default": "Menu"
|
||||
},
|
||||
"position": {
|
||||
"type": "string",
|
||||
"description": "Horizontal position of the button in the bar",
|
||||
"default": "right",
|
||||
"enum": ["right", "left"]
|
||||
},
|
||||
"animation-type": {
|
||||
"type": "string",
|
||||
"default": "slide_down",
|
||||
"description": "Animation type for menu",
|
||||
"enum": ["slide_down", "slide_up", "none"]
|
||||
},
|
||||
"animation-duration":{
|
||||
"type": "integer",
|
||||
"default": 250,
|
||||
"description": "Duration of animation in milliseconds"
|
||||
},
|
||||
"actions": {
|
||||
"$ref" : "#/widgets/buttons-grid/properties/actions"
|
||||
}
|
||||
}
|
||||
},
|
||||
"^buttons(#[a-zA-Z0-9_-]{1,}){0,1}?$": {
|
||||
"type": "object",
|
||||
"description": "A list of action-buttons to be displayed in the topbar",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"position": {
|
||||
"type": "string",
|
||||
"description": "Horizontal position of the button in the bar",
|
||||
"default": "right",
|
||||
"enum": ["right", "left"]
|
||||
},
|
||||
"actions": {
|
||||
"$ref" : "#/widgets/buttons-grid/properties/actions"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"volume": {
|
||||
"type": "object",
|
||||
"description": "Slider to control pulse volume",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Text displayed in front of the volume slider",
|
||||
"default": "Volume"
|
||||
},
|
||||
"show-per-app": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Show per app volume control"
|
||||
},
|
||||
"show-per-app-icon": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Show application icon in per app control"
|
||||
},
|
||||
"show-per-app-label": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Show application name in per app control"
|
||||
},
|
||||
"empty-list-label": {
|
||||
"type": "string",
|
||||
"default": "No active sink input",
|
||||
"description": "Text displayed when there are not active sink inputs"
|
||||
},
|
||||
"expand-button-label": {
|
||||
"type": "string",
|
||||
"default": "⇧",
|
||||
"description": "Label displayed on button to show per app volume control"
|
||||
},
|
||||
"collapse-button-label": {
|
||||
"type": "string",
|
||||
"default": "⇩",
|
||||
"description": "Label displayed on button to hide per app volume control"
|
||||
},
|
||||
"icon-size": {
|
||||
"type": "integer",
|
||||
"default": 24,
|
||||
"description": "Size of the application icon in per app volume control"
|
||||
},
|
||||
"animation-type": {
|
||||
"type": "string",
|
||||
"default": "slide_down",
|
||||
"description": "Animation type for menu",
|
||||
"enum": ["slide_down", "slide_up", "none"]
|
||||
},
|
||||
"animation-duration":{
|
||||
"type": "integer",
|
||||
"default": 250,
|
||||
"description": "Duration of animation in milliseconds"
|
||||
}
|
||||
}
|
||||
},
|
||||
"backlight": {
|
||||
"type": "object",
|
||||
"description": "Slider to control monitor brightness",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Text displayed in front of the backlight slider",
|
||||
"default": "Brightness"
|
||||
},
|
||||
"device": {
|
||||
"type": "string",
|
||||
"description": "Name of monitor (find possible devices using `ls /sys/class/backlight` or `ls /sys/class/leds`)",
|
||||
"default": "intel_backlight"
|
||||
},
|
||||
"subsystem": {
|
||||
"type": "string",
|
||||
"description": "Kernel subsystem for brightness control",
|
||||
"default": "backlight",
|
||||
"enum": ["backlight", "leds"]
|
||||
},
|
||||
"min": {
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Lowest possible value for brightness"
|
||||
}
|
||||
}
|
||||
},
|
||||
"inhibitors": {
|
||||
"type": "object",
|
||||
"description": "Control Center Inhibitors Widget",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "The title of the widget",
|
||||
"default": "Inhibitors"
|
||||
},
|
||||
"clear-all-button": {
|
||||
"type": "boolean",
|
||||
"description": "Wether to display a \"Clear All\" button",
|
||||
"default": true
|
||||
},
|
||||
"button-text": {
|
||||
"type": "string",
|
||||
"description": "\"Clear All\" button text",
|
||||
"default": "Clear All"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
204
projects/go/change-wallpaper/change-wallpaper.go
Normal file
204
projects/go/change-wallpaper/change-wallpaper.go
Normal file
@ -0,0 +1,204 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
wallhavenAPI = "https://wallhaven.cc/api/v1"
|
||||
wallpaperDir = "Pictures/wallpapers/wallhaven"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Topics []string `json:"topics"`
|
||||
}
|
||||
|
||||
var defaultTopics = []string{
|
||||
"132262 - Mobuseka",
|
||||
"konosuba",
|
||||
"bunny girl senpai",
|
||||
"oshi no ko",
|
||||
"kill la kill",
|
||||
"lofi",
|
||||
"eminence in shadow",
|
||||
}
|
||||
|
||||
func loadConfig() []string {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return defaultTopics
|
||||
}
|
||||
configPath := filepath.Join(homeDir, ".config", "change-wallpaper", "config.json")
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
return defaultTopics
|
||||
}
|
||||
defer file.Close()
|
||||
var cfg Config
|
||||
if err := json.NewDecoder(file).Decode(&cfg); err != nil || len(cfg.Topics) == 0 {
|
||||
return defaultTopics
|
||||
}
|
||||
return cfg.Topics
|
||||
}
|
||||
|
||||
type WallhavenResponse struct {
|
||||
Data []struct {
|
||||
Path string `json:"path"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Initialize random source
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
// Load topics from config or use defaults
|
||||
topics := loadConfig()
|
||||
|
||||
// Check if a file path was provided as argument
|
||||
if len(os.Args) > 1 {
|
||||
imgPath := os.Args[1]
|
||||
if _, err := os.Stat(imgPath); err == nil {
|
||||
changeWallpaper(imgPath, "")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create wallpaper directory if it doesn't exist
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting home directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
wallpaperPath := filepath.Join(homeDir, wallpaperDir)
|
||||
if err := os.MkdirAll(wallpaperPath, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating wallpaper directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Download and set new wallpaper
|
||||
newWallpaper, topic := downloadRandomWallpaper(wallpaperPath, r, topics)
|
||||
if newWallpaper != "" {
|
||||
changeWallpaper(newWallpaper, topic)
|
||||
} else {
|
||||
notify("Failed to download new wallpaper", "critical")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadRandomWallpaper(wallpaperPath string, r *rand.Rand, topics []string) (string, string) {
|
||||
// Select random topic
|
||||
topic := topics[r.Intn(len(topics))]
|
||||
var query string
|
||||
var displayName string
|
||||
|
||||
// Check if the topic is a tag ID with name
|
||||
if tagRegex := regexp.MustCompile(`^(\d+)\s*-\s*(.+)$`); tagRegex.MatchString(topic) {
|
||||
matches := tagRegex.FindStringSubmatch(topic)
|
||||
query = fmt.Sprintf("id:%s", matches[1])
|
||||
displayName = strings.TrimSpace(matches[2])
|
||||
} else {
|
||||
query = url.QueryEscape(topic)
|
||||
displayName = topic
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Searching for wallpapers related to: %s\n", displayName)
|
||||
|
||||
// Get wallpapers from Wallhaven API
|
||||
resp, err := http.Get(fmt.Sprintf("%s/search?q=%s&purity=100&categories=110&sorting=random", wallhavenAPI, query))
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error fetching from Wallhaven: %v\n", err)
|
||||
return "", ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Parse response
|
||||
var wallhavenResp WallhavenResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&wallhavenResp); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
if len(wallhavenResp.Data) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "No wallpapers found for topic: %s\n", displayName)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// Select a random image from the results
|
||||
randomIndex := r.Intn(len(wallhavenResp.Data))
|
||||
wallpaperURL := wallhavenResp.Data[randomIndex].Path
|
||||
filename := filepath.Base(wallpaperURL)
|
||||
filepath := filepath.Join(wallpaperPath, filename)
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Downloading: %s\n", filename)
|
||||
|
||||
resp, err = http.Get(wallpaperURL)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error downloading wallpaper: %v\n", err)
|
||||
return "", ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
file, err := os.Create(filepath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating file: %v\n", err)
|
||||
return "", ""
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if _, err := io.Copy(file, resp.Body); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error saving wallpaper: %v\n", err)
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return filepath, displayName
|
||||
}
|
||||
|
||||
func changeWallpaper(wallpaperPath, topic string) {
|
||||
// Save current wallpaper path
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
wallpaperFile := filepath.Join(homeDir, ".wallpaper")
|
||||
if err := os.WriteFile(wallpaperFile, []byte(wallpaperPath), 0644); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error saving wallpaper path: %v\n", err)
|
||||
}
|
||||
|
||||
// Change wallpaper using hyprctl
|
||||
cmd := exec.Command("hyprctl", "hyprpaper", "reload", ","+wallpaperPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error changing wallpaper: %v\n", err)
|
||||
}
|
||||
|
||||
// Send notification with wallpaper as icon
|
||||
filename := filepath.Base(wallpaperPath)
|
||||
message := fmt.Sprintf("Wallpaper changed to %s", filename)
|
||||
if topic != "" {
|
||||
message += fmt.Sprintf(" (%s)", topic)
|
||||
}
|
||||
notifyWithIcon(message, "normal", wallpaperPath)
|
||||
}
|
||||
|
||||
func notify(message, urgency string) {
|
||||
cmd := exec.Command("notify-send", "-i", "hyprpaper", "-u", urgency, "change-wallpaper.go", message)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error sending notification: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// notifyWithIcon sends a notification with a custom icon (wallpaper image)
|
||||
func notifyWithIcon(message, urgency, iconPath string) {
|
||||
cmd := exec.Command("notify-send", "-i", iconPath, "-u", urgency, "change-wallpaper.go", message)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error sending notification: %v\n", err)
|
||||
}
|
||||
}
|
3
projects/go/screenshot/go.mod
Normal file
3
projects/go/screenshot/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module gitea.suda.codes/sudacode/screenshot
|
||||
|
||||
go 1.24.2
|
113
projects/go/screenshot/screenshot.go
Normal file
113
projects/go/screenshot/screenshot.go
Normal file
@ -0,0 +1,113 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
scriptName = filepath.Base(os.Args[0])
|
||||
tmpDir = os.TempDir()
|
||||
defaultFilename = "screenshot.png"
|
||||
tmpScreenshot = filepath.Join(tmpDir, defaultFilename)
|
||||
requirements = []string{"grim", "slurp", "rofi", "zenity", "wl-copy", "jq", "hyprctl", "swappy", "notify-send"}
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
Desc string
|
||||
Cmd []string
|
||||
}
|
||||
|
||||
func notify(body, title string) {
|
||||
if title == "" {
|
||||
title = scriptName
|
||||
}
|
||||
cmd := exec.Command("notify-send", title, body)
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "notify error: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func checkDeps() {
|
||||
for _, cmd := range requirements {
|
||||
if _, err := exec.LookPath(cmd); err != nil {
|
||||
log.Fatalf("Error: %s is not installed. Please install it first.", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
checkDeps()
|
||||
options := []Option{
|
||||
{"1. Select a region and save", []string{"sh", "-c", fmt.Sprintf("slurp | grim -g - '%s'", tmpScreenshot)}},
|
||||
{"2. Select a region and copy to clipboard", []string{"sh", "-c", "slurp | grim -g - - | wl-copy"}},
|
||||
{"3. Whole screen", []string{"grim", tmpScreenshot}},
|
||||
{"4. Current window", []string{"sh", "-c", fmt.Sprintf("hyprctl -j activewindow | jq -r '\\.at[0],(\\.at[1]) \\.size[0]x(\\.size[1])' | grim -g - '%s'", tmpScreenshot)}},
|
||||
{"5. Edit", []string{"sh", "-c", "slurp | grim -g - - | swappy -f -"}},
|
||||
{"6. Quit", []string{"true"}},
|
||||
}
|
||||
|
||||
var menu bytes.Buffer
|
||||
for _, opt := range options {
|
||||
menu.WriteString(opt.Desc)
|
||||
menu.WriteByte('\n')
|
||||
}
|
||||
|
||||
rofi := exec.Command("rofi", "-dmenu", "-i", "-p", "Enter option or select from the list", "-mesg", "Select a Screenshot Option", "-format", "i", "-theme-str", "listview {columns: 2; lines: 3;} window {width: 55%;}", "-yoffset", "30", "-xoffset", "30", "-a", "0", "-no-custom", "-location", "0")
|
||||
rofi.Stdin = &menu
|
||||
out, err := rofi.Output()
|
||||
if err != nil {
|
||||
notify("No option selected.", "")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
choiceStr := strings.TrimSpace(string(out))
|
||||
idx, err := strconv.Atoi(choiceStr)
|
||||
if err != nil || idx < 0 || idx >= len(options) {
|
||||
notify("No option selected.", "")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
selected := options[idx]
|
||||
if idx == 1 {
|
||||
if err := exec.Command(selected.Cmd[0], selected.Cmd[1:]...).Run(); err != nil {
|
||||
notify("An error occurred while taking the screenshot.", "")
|
||||
os.Exit(1)
|
||||
}
|
||||
notify("Screenshot copied to clipboard", "")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if err := exec.Command(selected.Cmd[0], selected.Cmd[1:]...).Run(); err != nil {
|
||||
notify("An error occurred while taking the screenshot.", "")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if idx == 5 {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
notify("Screenshot saved temporarily.\nChoose where to save it permanently", "")
|
||||
zenity := exec.Command("zenity", "--file-selection", "--title=Save Screenshot", "--filename="+defaultFilename, "--save")
|
||||
fileOut, err := zenity.Output()
|
||||
if err != nil {
|
||||
os.Remove(tmpScreenshot)
|
||||
notify("Screenshot discarded", "")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
dest := strings.TrimSpace(string(fileOut))
|
||||
if err := os.Rename(tmpScreenshot, dest); err != nil {
|
||||
notify(fmt.Sprintf("Failed to save screenshot to %s", dest), "")
|
||||
} else {
|
||||
notify(fmt.Sprintf("Screenshot saved to %s", dest), "")
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user