├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── demo.png └── xcorners.c /.gitignore: -------------------------------------------------------------------------------- 1 | xcorners 2 | *.o 3 | *.out 4 | *.a 5 | *.d 6 | compile_commands.json 7 | .cache/ 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Spydr06 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | XCORNERS := xcorners 2 | 3 | X11_CFLAGS = $(shell pkg-config --cflags x11 xfixes cairo) 4 | X11_LIBS = $(shell pkg-config --libs x11 xfixes cairo) 5 | 6 | CFLAGS += -Wall -Wextra -pedantic -std=gnu99 $(X11_CFLAGS) 7 | LDFLAGS += $(X11_LIBS) 8 | 9 | PREFIX ?= /usr 10 | EXEC_PREFIX ?= $(PREFIX)/bin 11 | 12 | .PHONY: all 13 | all: $(XCORNERS) 14 | 15 | $(XCORNERS): *.c 16 | $(CC) $(CFLAGS) -MMD -MP $^ -o $@ $(LDFLAGS) 17 | 18 | .PHONY: install 19 | install: $(XCORNERS) 20 | install -v -D -m 755 $^ $(EXEC_PREFIX)/$(XCORNERS) 21 | 22 | .PHONY: uninstall 23 | uninstall: 24 | rm -f $(EXEC_PREFIX)/$(XCORNERS) 25 | 26 | .PHONY: clean 27 | clean: 28 | rm $(XCORNERS) *.d 29 | 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xcorners 2 | 3 | A small utility to draw rounded screen corners on X11 window managers. 4 | 5 | > `xcorners` with polybar on bspwm: 6 | > ![demo.png](./demo.png) 7 | 8 | ## Usage 9 | 10 | ``` 11 | $ xcorners --help 12 | Usage: ./xcorners [OPTIONS] 13 | 14 | Options: 15 | -W Set the horizontal space between the corners. [3000] 16 | -H Set the vertical space between the corners. [1920] 17 | -x Set the horizontal offset. [0] 18 | -y Set the vertical offset. [0] 19 | -r Set the corner radius. [12] 20 | -t, -T Enable or disable top corners. [enabled] 21 | -b, -B Enable or disable bottom corners. [disabled] 22 | -c Set the corner color. [000000ff] 23 | -1 Only allow one instance. 24 | -h, --help Print this help text and exit. 25 | ``` 26 | 27 | > [!IMPORTANT] 28 | > If you experience issues with (partially) black screens, you'll need to enable a transparancy-capable x11 compositor. 29 | 30 | > Example usage: My 31 | > [bspwm](https://github.com/Spydr06/dotfiles/blob/b9833f142d992542564b92e94364ea79582aa530/.config/bspwm/bspwmrc#L31C1-L31C41) and 32 | > [sxhkd](https://github.com/Spydr06/dotfiles/blob/b9833f142d992542564b92e94364ea79582aa530/.config/sxhkd/sxhkdrc#L89-L94) configuration. 33 | 34 | ## Install 35 | 36 | If you are using Arch Linux, you can install `xcorners` from the [`xcorners-git`](https://aur.archlinux.org/packages/xcorners-git) AUR package. 37 | 38 | Alternatively, you can build it from source. 39 | 40 | **Dependencies:** 41 | 42 | - `gcc` 43 | - `make` 44 | - `libX11`, `libXfixes`, `libCairo` (install `-dev` or `-devel` packages) 45 | 46 | **Building:** 47 | 48 | After you cloned the repository, use `make` to build `xcorners`: 49 | 50 | ```sh 51 | $ make 52 | ``` 53 | 54 | If successful, this creates the executable `./xcorners`. 55 | 56 | **Installation:** 57 | 58 | ``` 59 | # make install 60 | ``` 61 | 62 | This installs `xcorners` to `/usr/bin`, if you want another prefix, set it using `PREFIX=""` or `EXEC_PREFIX=""`. 63 | 64 | ## TO-DO 65 | 66 | - [x] Basic functionality 67 | - [x] Option to not launch when instance is already running 68 | - [ ] Automatic screen detection and configuration 69 | - [ ] Automatic fullscreen detection 70 | - [ ] Bugtesting 71 | 72 | > If you encounter bugs, have more ideas or want to expand the functionality, feel free to open an issue or pull request :D 73 | 74 | ## License 75 | 76 | `xcorners` is licensed under the MIT License. See [./LICENSE](./LICENSE) or the source for more information. 77 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Spydr06/xcorners/219c26140d1c0c53663c463b2bd3453421bb6773/demo.png -------------------------------------------------------------------------------- /xcorners.c: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2024 Spydr06 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include 40 | #include 41 | 42 | #define XCORNERS_REPO_URL "https://github.com/Spydr06/xcorners" 43 | 44 | #define X_CLASS_NAME "xcorners" 45 | #define DEFAULT_RADIUS 12 46 | 47 | #define _(str) str 48 | #define PANIC(...) do { \ 49 | fprintf(stderr, __VA_ARGS__); \ 50 | exit(EXIT_FAILURE); \ 51 | } while(0) 52 | 53 | #define UNSIGNED_OPT(c, v, base) do { \ 54 | (v) = (unsigned int) strtoul(optarg, NULL, (base)); \ 55 | if(errno) \ 56 | PANIC("%s: option -- '%c' expects numeric argument: %s\n", *argv, (c), strerror(errno)); \ 57 | } while(0) 58 | 59 | unsigned width = 0, height = 0, radius = DEFAULT_RADIUS, x_offset = 0, y_offset = 0, color = 0x000000ff; 60 | bool top = true, bottom = false; 61 | 62 | static const struct option cmdline_options[] = { 63 | {"help", 0, NULL, 'h'}, 64 | {NULL, 0, NULL, 0} 65 | }; 66 | 67 | __attribute__((noreturn)) 68 | static void help(const char* pname) { 69 | printf(_("Usage: %s [OPTIONS]\n\ 70 | \n\ 71 | Options [default]:\n\ 72 | -W Set the horizontal space between the corners. [%u]\n\ 73 | -H Set the vertical space between the corners. [%u]\n\ 74 | -x Set the horizontal offset. [%u]\n\ 75 | -y Set the vertical offset. [%u]\n\ 76 | -r Set the corner radius. [%u]\n\ 77 | -t, -T Enable or disable top corners. [%s]\n\ 78 | -b, -B Enable or disable bottom corners. [%s]\n\ 79 | -c Set the corner color. [%08x]\n\ 80 | -1 Only allow one instance.\n\ 81 | -h, --help Print this help text and exit.\n\ 82 | \n\ 83 | Copyright (c) 2024 Spydr06\n\ 84 | %s is licensed under the MIT License; see the source for copying conditions.\n\ 85 | \n\ 86 | Repository: \n" 87 | " " XCORNERS_REPO_URL "\n\ 88 | \n\ 89 | "), 90 | pname, width, height, x_offset, y_offset, radius, 91 | top ? "enabled" : "disabled", bottom ? "enabled" : "disabled", 92 | color, 93 | pname 94 | ); 95 | 96 | exit(EXIT_SUCCESS); 97 | } 98 | 99 | static void check_other_instances(Display* d, Window root) 100 | { 101 | Window root_return, parent_return, *children; 102 | unsigned num_children; 103 | if(!XQueryTree(d, root, &root_return, &parent_return, &children, &num_children)) 104 | return; 105 | 106 | for(unsigned i = 0; i < num_children; i++) { 107 | XClassHint class_hint; 108 | if(!XGetClassHint(d, children[i], &class_hint)) 109 | continue; 110 | 111 | if(strcmp(class_hint.res_class, X_CLASS_NAME) == 0) { 112 | XCloseDisplay(d); 113 | printf("Another instance is already running, terminating as indicated by `-1`.\n"); 114 | exit(0); 115 | } 116 | 117 | XFree(class_hint.res_name); 118 | XFree(class_hint.res_class); 119 | } 120 | 121 | XFree(children); 122 | } 123 | 124 | static void draw(cairo_t* c) { 125 | if(top) { 126 | cairo_move_to(c, 0, 0); // top left 127 | cairo_arc(c, radius, radius, radius, -M_PI, -M_PI / 2); 128 | cairo_line_to(c, 0, 0); 129 | cairo_line_to(c, 0, radius); 130 | 131 | cairo_move_to(c, width, 0); // top right 132 | cairo_arc(c, width - radius, radius, radius, -M_PI / 2, 0); 133 | cairo_line_to(c, width, 0); 134 | cairo_line_to(c, width - radius, 0); 135 | } 136 | 137 | if(bottom) { 138 | cairo_move_to(c, 0, height); // bottom left 139 | cairo_arc(c, radius, height - radius, radius, M_PI / 2, M_PI); 140 | cairo_line_to(c, 0, height); 141 | cairo_line_to(c, 0, height - radius); 142 | 143 | cairo_move_to(c, width, height); // bottom right 144 | cairo_arc(c, width - radius, height - radius, radius, 0, M_PI / 2); 145 | cairo_line_to(c, width, height); 146 | cairo_line_to(c, width - radius, height); 147 | } 148 | 149 | cairo_set_source_rgba(c, 150 | (double)((color >> 24) & 0xff) / 255.0, 151 | (double)((color >> 16) & 0xff) / 255.0, 152 | (double)((color >> 8) & 0xff) / 255.0, 153 | (double)(color & 0xff) / 255.0 154 | ); 155 | cairo_fill(c); 156 | } 157 | 158 | int main(int argc, char** argv) { 159 | Display* d = XOpenDisplay(NULL); 160 | if(!d) 161 | PANIC("X11 Error: %s\n", strerror(errno)); 162 | 163 | int s = DefaultScreen(d); 164 | Window root = RootWindow(d, s); 165 | 166 | width = DisplayWidth(d, s); 167 | height = DisplayHeight(d, s); 168 | 169 | errno = 0; 170 | int ch; 171 | while((ch = getopt_long(argc, argv, "hx:y:W:H:r:tTbBc:1", cmdline_options, NULL)) != EOF) { 172 | switch(ch) 173 | { 174 | case 'h': 175 | help(*argv); 176 | case 'x': 177 | UNSIGNED_OPT('x', x_offset, 10); 178 | break; 179 | case 'y': 180 | UNSIGNED_OPT('y', y_offset, 10); 181 | break; 182 | case 'W': 183 | UNSIGNED_OPT('W', width, 10); 184 | break; 185 | case 'H': 186 | UNSIGNED_OPT('H', height, 10); 187 | break; 188 | case 'r': 189 | UNSIGNED_OPT('r', radius, 10); 190 | break; 191 | case 'c': 192 | UNSIGNED_OPT('c', color, 16); 193 | break; 194 | case 't': 195 | top = true; 196 | break; 197 | case 'T': 198 | top = false; 199 | break; 200 | case 'b': 201 | bottom = true; 202 | break; 203 | case 'B': 204 | bottom = false; 205 | break; 206 | case '1': 207 | check_other_instances(d, root); 208 | break; 209 | default: 210 | PANIC("%s: invalid option -- '%c'\nTry `%s --help` for more information.\n", *argv, ch, *argv); 211 | } 212 | } 213 | 214 | if(optind < argc) 215 | PANIC("%s: invalid option -- '%s'\nTry `%s --help` for more information.\n", *argv, argv[optind], *argv); 216 | 217 | 218 | XVisualInfo vinfo; 219 | if(!XMatchVisualInfo(d, s, 32, TrueColor, &vinfo)) 220 | PANIC("%s: no visual found supporting 32 bit color.\n", *argv); 221 | 222 | XSetWindowAttributes attrs = { 223 | .override_redirect = true, 224 | .colormap = XCreateColormap(d, root, vinfo.visual, AllocNone), 225 | .background_pixel = 0, 226 | .border_pixel = 0 227 | }; 228 | 229 | Window window = XCreateWindow( 230 | d, root, 231 | x_offset, y_offset, width, height, 0, 232 | vinfo.depth, InputOutput, 233 | vinfo.visual, 234 | CWOverrideRedirect | CWColormap | CWBackPixel | CWBorderPixel, 235 | &attrs 236 | ); 237 | 238 | XSetClassHint(d, window, 239 | &(XClassHint){.res_name = X_CLASS_NAME, .res_class = X_CLASS_NAME} 240 | ); 241 | 242 | XSelectInput(d, window, ExposureMask | PropertyChangeMask); 243 | 244 | XserverRegion region = XFixesCreateRegion(d, NULL, 0); 245 | XFixesSetWindowShapeRegion(d, window, ShapeInput, x_offset, y_offset, region); 246 | XFixesDestroyRegion(d, region); 247 | 248 | XMapWindow(d, window); 249 | 250 | XLowerWindow(d, window); 251 | 252 | cairo_surface_t* surface = cairo_xlib_surface_create(d, window, vinfo.visual, width, height); 253 | cairo_t* cr = cairo_create(surface); 254 | 255 | XEvent ev; 256 | while(1) { 257 | XNextEvent(d, &ev); 258 | switch(ev.type) { 259 | case Expose: 260 | draw(cr); 261 | cairo_surface_flush(surface); 262 | XFlush(d); 263 | break; 264 | default: 265 | #ifndef NDEBUG 266 | printf("unhandled event: %d\n", ev.type); 267 | #endif 268 | break; 269 | } 270 | } 271 | 272 | cairo_destroy(cr); 273 | cairo_surface_destroy(surface); 274 | 275 | XUnmapWindow(d, window); 276 | XCloseDisplay(d); 277 | 278 | return EXIT_SUCCESS; 279 | } 280 | 281 | --------------------------------------------------------------------------------