├── po ├── POTFILES.in ├── LINGUAS ├── meson.build ├── labwc.pot ├── tr.po ├── sv.po ├── it.po ├── es.po ├── de.po ├── id.po ├── pl.po ├── ru.po └── ka.po ├── scripts ├── .gitignore ├── find-banned.sh ├── helper │ └── Makefile ├── README.md └── check ├── subprojects ├── .gitignore ├── seatd.wrap └── wlroots.wrap ├── src ├── menu │ └── meson.build ├── decorations │ ├── meson.build │ ├── xdg-deco.c │ └── kde-deco.c ├── config │ ├── meson.build │ ├── libinput.c │ └── session.c ├── input │ ├── meson.build │ ├── input.c │ ├── gestures.c │ └── key-state.c ├── ssd │ ├── meson.build │ └── ssd_border.c ├── button │ ├── meson.build │ ├── common.c │ ├── button-png.c │ └── button-svg.c ├── common │ ├── file-helpers.c │ ├── match.c │ ├── meson.build │ ├── grab-file.c │ ├── nodename.c │ ├── mem.c │ ├── parse-bool.c │ ├── fd_util.c │ ├── string-helpers.c │ ├── spawn.c │ ├── scene-helpers.c │ ├── buf.c │ ├── graphic-helpers.c │ ├── scaled_font_buffer.c │ ├── dir.c │ └── font.c ├── meson.build ├── node.c ├── idle.c ├── xdg-popup.c ├── view-impl-common.c ├── dnd.c ├── window-rules.c ├── foreign.c └── buffer.c ├── include ├── meson.build ├── debug.h ├── common │ ├── fd_util.h │ ├── border.h │ ├── spawn.h │ ├── dir.h │ ├── file-helpers.h │ ├── grab-file.h │ ├── match.h │ ├── nodename.h │ ├── scene-helpers.h │ ├── list.h │ ├── parse-bool.h │ ├── string-helpers.h │ ├── buf.h │ ├── array.h │ ├── font.h │ ├── graphic-helpers.h │ ├── mem.h │ ├── macros.h │ ├── scaled_font_buffer.h │ └── scaled_scene_buffer.h ├── input │ ├── touch.h │ ├── gestures.h │ ├── input.h │ ├── keyboard.h │ ├── key-state.h │ └── cursor.h ├── button │ ├── button-png.h │ ├── button-svg.h │ ├── button-xbm.h │ └── common.h ├── idle.h ├── resistance.h ├── dnd.h ├── decorations.h ├── resize_indicator.h ├── snap.h ├── config │ ├── session.h │ ├── libinput.h │ ├── keybind.h │ ├── mousebind.h │ └── rcxml.h ├── session-lock.h ├── view-impl-common.h ├── workspaces.h ├── layers.h ├── window-rules.h ├── action.h ├── buffer.h ├── xwayland.h ├── node.h ├── regions.h ├── ssd.h ├── menu │ └── menu.h ├── theme.h └── ssd-internal.h ├── docs ├── labwc.desktop ├── README ├── meson.build ├── rc.xml ├── autostart ├── labwc.1.scd ├── labwc-menu.5.scd ├── menu.xml ├── themerc └── environment ├── .gitattributes ├── .editorconfig ├── meson_options.txt ├── .github └── workflows │ ├── labwc.github.io.yml │ └── irc.yml ├── protocols ├── meson.build └── wlr-input-inhibitor-unstable-v1.xml └── meson.build /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | src/menu/menu.c 2 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | de es id it ka pl ru sv tr 2 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | find-banned 3 | -------------------------------------------------------------------------------- /subprojects/.gitignore: -------------------------------------------------------------------------------- 1 | /* 2 | !/*.wrap 3 | -------------------------------------------------------------------------------- /src/menu/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources += files( 2 | 'menu.c', 3 | ) 4 | -------------------------------------------------------------------------------- /include/meson.build: -------------------------------------------------------------------------------- 1 | configure_file(output: 'config.h', configuration: conf_data) 2 | -------------------------------------------------------------------------------- /src/decorations/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources += files( 2 | 'kde-deco.c', 3 | 'xdg-deco.c', 4 | ) 5 | -------------------------------------------------------------------------------- /subprojects/seatd.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url=https://git.sr.ht/~kennylevinsen/seatd 3 | revision=0.6.4 4 | 5 | -------------------------------------------------------------------------------- /src/config/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources += files( 2 | 'rcxml.c', 3 | 'keybind.c', 4 | 'session.c', 5 | 'mousebind.c', 6 | 'libinput.c', 7 | ) 8 | -------------------------------------------------------------------------------- /docs/labwc.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=labwc 3 | Comment=A wayland stacking compositor 4 | Exec=labwc 5 | Type=Application 6 | DesktopNames=wlroots 7 | -------------------------------------------------------------------------------- /src/input/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources += files( 2 | 'cursor.c', 3 | 'gestures.c', 4 | 'input.c', 5 | 'keyboard.c', 6 | 'key-state.c', 7 | 'touch.c', 8 | ) 9 | -------------------------------------------------------------------------------- /subprojects/wlroots.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | url = https://gitlab.freedesktop.org/wlroots/wlroots.git 3 | revision = 0.17 4 | 5 | [provide] 6 | dependency_names = wlroots 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Exclude checkpatch.pl from language stats 2 | # https://github.com/github/linguist/blob/master/docs/overrides.md 3 | scripts/checkpatch.pl linguist-vendored 4 | -------------------------------------------------------------------------------- /src/ssd/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources += files( 2 | 'resize_indicator.c', 3 | 'ssd.c', 4 | 'ssd_part.c', 5 | 'ssd_titlebar.c', 6 | 'ssd_border.c', 7 | 'ssd_extents.c', 8 | ) 9 | -------------------------------------------------------------------------------- /docs/README: -------------------------------------------------------------------------------- 1 | Config layout for ~/.config/labwc/ 2 | - autostart 3 | - environment 4 | - menu.xml 5 | - rc.xml 6 | - themerc-override 7 | 8 | See `man labwc-config and `man labwc-theme` for further details. 9 | 10 | -------------------------------------------------------------------------------- /src/button/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources += files( 2 | 'button-png.c', 3 | 'button-xbm.c', 4 | 'common.c', 5 | ) 6 | 7 | if have_rsvg 8 | labwc_sources += files( 9 | 'button-svg.c', 10 | ) 11 | endif 12 | 13 | -------------------------------------------------------------------------------- /include/debug.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_DEBUG_H 3 | #define LABWC_DEBUG_H 4 | 5 | struct server; 6 | 7 | void debug_dump_scene(struct server *server); 8 | 9 | #endif /* LABWC_DEBUG_H */ 10 | -------------------------------------------------------------------------------- /include/common/fd_util.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_FD_UTIL_H 3 | #define LABWC_FD_UTIL_H 4 | 5 | void increase_nofile_limit(void); 6 | void restore_nofile_limit(void); 7 | 8 | #endif /* LABWC_FD_UTIL_H */ 9 | -------------------------------------------------------------------------------- /scripts/find-banned.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | banned="malloc,g_strcmp0,sprintf,vsprintf,strcpy,strncpy,strcat,strncat" 4 | 5 | find src/ include/ \( -name "*.c" -o -name "*.h" \) -type f \ 6 | | ./scripts/helper/find-idents --tokens=$banned - 7 | -------------------------------------------------------------------------------- /scripts/helper/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -g -Wall -O0 -std=c11 2 | LDFLAGS += -fsanitize=address 3 | 4 | PROGS = find-idents 5 | 6 | all: $(PROGS) 7 | 8 | find-idents: find-idents.o 9 | $(CC) -o $@ $^ 10 | 11 | clean : 12 | $(RM) $(PROGS) *.o 13 | -------------------------------------------------------------------------------- /src/common/file-helpers.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include "common/file-helpers.h" 4 | 5 | bool 6 | file_exists(const char *filename) 7 | { 8 | struct stat st; 9 | return (!stat(filename, &st)); 10 | } 11 | -------------------------------------------------------------------------------- /include/common/border.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_BORDER_H 3 | #define LABWC_BORDER_H 4 | 5 | struct border { 6 | int top; 7 | int right; 8 | int bottom; 9 | int left; 10 | }; 11 | 12 | #endif /* LABWC_BORDER_H */ 13 | -------------------------------------------------------------------------------- /src/common/match.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | #include 4 | #include "common/match.h" 5 | 6 | bool 7 | match_glob(const char *pattern, const char *string) 8 | { 9 | return fnmatch(pattern, string, FNM_CASEFOLD) == 0; 10 | } 11 | -------------------------------------------------------------------------------- /include/input/touch.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_TOUCH_H 3 | #define LABWC_TOUCH_H 4 | 5 | struct seat; 6 | 7 | void touch_init(struct seat *seat); 8 | void touch_finish(struct seat *seat); 9 | 10 | #endif /* LABWC_TOUCH_H */ 11 | -------------------------------------------------------------------------------- /include/input/gestures.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_GESTURES_H 3 | #define LABWC_GESTURES_H 4 | 5 | struct seat; 6 | 7 | void gestures_init(struct seat *seat); 8 | void gestures_finish(struct seat *seat); 9 | 10 | #endif /* LABWC_GESTURES_H */ 11 | -------------------------------------------------------------------------------- /include/input/input.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_INPUT_H 3 | #define LABWC_INPUT_H 4 | 5 | struct seat; 6 | 7 | void input_handlers_init(struct seat *seat); 8 | void input_handlers_finish(struct seat *seat); 9 | 10 | #endif /* LABWC_INPUT_H */ 11 | -------------------------------------------------------------------------------- /include/button/button-png.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_BUTTON_PNG_H 3 | #define LABWC_BUTTON_PNG_H 4 | 5 | struct lab_data_buffer; 6 | 7 | void button_png_load(const char *button_name, struct lab_data_buffer **buffer); 8 | 9 | #endif /* LABWC_BUTTON_PNG_H */ 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{c,h}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | indent_style = tab 9 | indent_size = 8 10 | max_line_length = 80 11 | 12 | [*.{xml,build}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | -------------------------------------------------------------------------------- /include/button/button-svg.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_BUTTON_SVG_H 3 | #define LABWC_BUTTON_SVG_H 4 | 5 | struct lab_data_buffer; 6 | 7 | void button_svg_load(const char *button_name, struct lab_data_buffer **buffer, 8 | int size); 9 | 10 | #endif /* LABWC_BUTTON_SVG_H */ 11 | -------------------------------------------------------------------------------- /include/common/spawn.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SPAWN_H 3 | #define LABWC_SPAWN_H 4 | 5 | /** 6 | * spawn_async_no_shell - execute asyncronously 7 | * @command: command to be executed 8 | */ 9 | void spawn_async_no_shell(char const *command); 10 | 11 | #endif /* LABWC_SPAWN_H */ 12 | -------------------------------------------------------------------------------- /src/button/common.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include "button/common.h" 4 | #include "common/dir.h" 5 | #include "config/rcxml.h" 6 | 7 | void 8 | button_filename(const char *name, char *buf, size_t len) 9 | { 10 | snprintf(buf, len, "%s/%s", theme_dir(rc.theme_name), name); 11 | } 12 | -------------------------------------------------------------------------------- /include/idle.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_IDLE_H 3 | #define LABWC_IDLE_H 4 | 5 | struct wl_display; 6 | struct wlr_seat; 7 | 8 | void idle_manager_create(struct wl_display *display, struct wlr_seat *wlr_seat); 9 | void idle_manager_notify_activity(struct wlr_seat *seat); 10 | 11 | #endif /* LABWC_IDLE_H */ 12 | -------------------------------------------------------------------------------- /include/resistance.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_RESISTANCE_H 3 | #define LABWC_RESISTANCE_H 4 | #include "labwc.h" 5 | 6 | void resistance_move_apply(struct view *view, double *x, double *y); 7 | void resistance_resize_apply(struct view *view, struct wlr_box *new_view_geo); 8 | 9 | #endif /* LABWC_RESISTANCE_H */ 10 | -------------------------------------------------------------------------------- /include/common/dir.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_DIR_H 3 | #define LABWC_DIR_H 4 | 5 | char *config_dir(void); 6 | 7 | /** 8 | * theme_dir - find theme directory containing theme @theme_name 9 | * @theme_name: theme to search for 10 | */ 11 | char *theme_dir(const char *theme_name); 12 | 13 | #endif /* LABWC_DIR_H */ 14 | -------------------------------------------------------------------------------- /include/common/file-helpers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_FILE_HELPERS_H 3 | #define LABWC_FILE_HELPERS_H 4 | #include 5 | 6 | /** 7 | * file_exists() - Test if file exists. 8 | * @filename: Name of file to test. 9 | */ 10 | bool file_exists(const char *filename); 11 | 12 | #endif /* LABWC_FILE_HELPERS_H */ 13 | -------------------------------------------------------------------------------- /src/common/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources += files( 2 | 'buf.c', 3 | 'dir.c', 4 | 'fd_util.c', 5 | 'file-helpers.c', 6 | 'font.c', 7 | 'grab-file.c', 8 | 'graphic-helpers.c', 9 | 'match.c', 10 | 'mem.c', 11 | 'nodename.c', 12 | 'parse-bool.c', 13 | 'scaled_font_buffer.c', 14 | 'scaled_scene_buffer.c', 15 | 'scene-helpers.c', 16 | 'spawn.c', 17 | 'string-helpers.c', 18 | ) 19 | -------------------------------------------------------------------------------- /src/input/input.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include "input/cursor.h" 3 | #include "input/input.h" 4 | #include "input/keyboard.h" 5 | 6 | void 7 | input_handlers_init(struct seat *seat) 8 | { 9 | cursor_init(seat); 10 | keyboard_init(seat); 11 | } 12 | 13 | void 14 | input_handlers_finish(struct seat *seat) 15 | { 16 | cursor_finish(seat); 17 | keyboard_finish(seat); 18 | } 19 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- 1 | option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') 2 | option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') 3 | option('svg', type: 'feature', value: 'enabled', description: 'Enable svg window buttons') 4 | option('nls', type: 'feature', value: 'auto', description: 'Enable native language support') 5 | -------------------------------------------------------------------------------- /include/dnd.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_DND_H 3 | #define LABWC_DND_H 4 | 5 | #include 6 | 7 | struct seat; 8 | 9 | void dnd_init(struct seat *seat); 10 | void dnd_icons_show(struct seat *seat, bool show); 11 | void dnd_icons_move(struct seat *seat, double x, double y); 12 | void dnd_finish(struct seat *seat); 13 | 14 | #endif /* LABWC_DND_H */ 15 | -------------------------------------------------------------------------------- /include/button/button-xbm.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_BUTTON_XBM_H 3 | #define LABWC_BUTTON_XBM_H 4 | 5 | struct lab_data_buffer; 6 | 7 | /* button_xbm_load - Convert xbm file to buffer with cairo surface */ 8 | void button_xbm_load(const char *button_name, const char *alt_name, 9 | struct lab_data_buffer **buffer, char *fallback_button, float *rgba); 10 | 11 | #endif /* LABWC_BUTTON_XBM_H */ 12 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n = import('i18n') 2 | add_project_arguments('-DGETTEXT_PACKAGE="' + meson.project_name() + '"', 3 | '-DLOCALEDIR="' + get_option('prefix') / get_option('localedir') + '"', 4 | language:'c') 5 | i18n.gettext(meson.project_name(), 6 | args: ['--directory=' + source_root, 7 | '--add-comments=TRANSLATORS', 8 | '--keyword=_', 9 | '--msgid-bugs=https://github.com/labwc/labwc/issues'], 10 | preset: 'glib' 11 | ) 12 | -------------------------------------------------------------------------------- /include/common/grab-file.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | /* 3 | * Read file into memory 4 | * 5 | * Copyright Johan Malm 2020 6 | */ 7 | 8 | #ifndef LABWC_GRAB_FILE_H 9 | #define LABWC_GRAB_FILE_H 10 | 11 | /** 12 | * grab_file - read file into memory buffer 13 | * @filename: file to read 14 | * Returns pointer to buffer. Free with free(). 15 | */ 16 | char *grab_file(const char *filename); 17 | 18 | #endif /* LABWC_GRAB_FILE_H */ 19 | -------------------------------------------------------------------------------- /include/common/match.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_MATCH_H 3 | #define LABWC_MATCH_H 4 | 5 | #include 6 | 7 | /** 8 | * match_glob() - Pattern match using '*' wildcards and '?' jokers. 9 | * @pattern: Pattern to match against. 10 | * @string: String to search. 11 | * Note: Comparison case-insensitive. 12 | */ 13 | bool match_glob(const char *pattern, const char *string); 14 | 15 | #endif /* LABWC_MATCH_H */ 16 | -------------------------------------------------------------------------------- /include/decorations.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_DECORATIONS_H 3 | #define LABWC_DECORATIONS_H 4 | 5 | struct server; 6 | struct view; 7 | struct wlr_surface; 8 | 9 | void kde_server_decoration_init(struct server *server); 10 | void xdg_server_decoration_init(struct server *server); 11 | 12 | void kde_server_decoration_update_default(void); 13 | void kde_server_decoration_set_view(struct view *view, struct wlr_surface *surface); 14 | 15 | #endif /* LABWC_DECORATIONS_H */ 16 | -------------------------------------------------------------------------------- /include/button/common.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_BUTTON_COMMON_H 3 | #define LABWC_BUTTON_COMMON_H 4 | 5 | /** 6 | * button_filename() - Get full filename for button. 7 | * @name: The name of the button (for example 'iconify.xbm'). 8 | * @buf: Buffer to fill with the full filename 9 | * @len: Length of buffer 10 | * 11 | * Example return value: /usr/share/themes/Numix/openbox-3/iconfify.xbm 12 | */ 13 | void button_filename(const char *name, char *buf, size_t len); 14 | 15 | #endif /* LABWC_BUTTON_COMMON_H */ 16 | -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | These scripts are intended to be run from the project top-level directory 2 | like this: `scripts/foo.sh` 3 | 4 | - `scripts/check`: wrapper to check all files in `src/` and `include/` 5 | 6 | - `scripts/checkpatch.pl`: Quick hack on the Linux kernel [checkpatch.pl] 7 | to lint C files written according to the labwc coding style. Run like 8 | this: `./checkpatch.pl --no-tree --terse --strict --file ` 9 | 10 | [checkpatch.pl]: https://raw.githubusercontent.com/torvalds/linux/4ce9f970457899defdf68e26e0502c7245002eb3/scripts/checkpatch.pl 11 | -------------------------------------------------------------------------------- /include/common/nodename.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_NODENAME_H 3 | #define LABWC_NODENAME_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * nodename - give xml node an ascii name 11 | * @node: xml-node 12 | * @buf: buffer to receive the name 13 | * @len: size of buffer 14 | * 15 | * For example, the xml structure would return the 16 | * name c.b.a 17 | */ 18 | char *nodename(xmlNode * node, char *buf, int len); 19 | 20 | #endif /* LABWC_NODENAME_H */ 21 | -------------------------------------------------------------------------------- /include/resize_indicator.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_RESIZE_INDICATOR_H 3 | #define LABWC_RESIZE_INDICATOR_H 4 | 5 | struct server; 6 | struct view; 7 | 8 | enum resize_indicator_mode { 9 | LAB_RESIZE_INDICATOR_NEVER = 0, 10 | LAB_RESIZE_INDICATOR_ALWAYS, 11 | LAB_RESIZE_INDICATOR_NON_PIXEL 12 | }; 13 | 14 | void resize_indicator_reconfigure(struct server *server); 15 | void resize_indicator_show(struct view *view); 16 | void resize_indicator_update(struct view *view); 17 | void resize_indicator_hide(struct view *view); 18 | 19 | #endif /* LABWC_RESIZE_INDICATOR_H */ 20 | -------------------------------------------------------------------------------- /.github/workflows/labwc.github.io.yml: -------------------------------------------------------------------------------- 1 | # Triggers a rebuild and deploy of labwc.github.io when man pages change 2 | # 3 | # https://stackoverflow.com/a/65514259 4 | 5 | name: "labwc.github.io" 6 | 7 | on: 8 | push: 9 | branches: 10 | - 'master' 11 | - 'v0.5_disabled' 12 | paths: 13 | - 'docs/*.scd' 14 | 15 | jobs: 16 | notify: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: labwc.github.io 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.WEB_DEPLOY_TOKEN }} 22 | run: | 23 | gh api repos/labwc/labwc.github.io/dispatches \ 24 | --raw-field event_type=rebuild 25 | -------------------------------------------------------------------------------- /include/snap.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SNAP_H 3 | #define LABWC_SNAP_H 4 | 5 | #include "common/border.h" 6 | #include "view.h" 7 | 8 | struct wlr_box; 9 | 10 | struct border snap_get_max_distance(struct view *view); 11 | 12 | void snap_vector_to_next_edge(struct view *view, enum view_edge direction, int *dx, int *dy); 13 | int snap_distance_to_next_edge(struct view *view, enum view_edge direction); 14 | void snap_grow_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo); 15 | void snap_shrink_to_next_edge(struct view *view, enum view_edge direction, struct wlr_box *geo); 16 | 17 | #endif /* LABWC_SNAP_H */ 18 | -------------------------------------------------------------------------------- /include/input/keyboard.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_KEYBOARD_H 3 | #define LABWC_KEYBOARD_H 4 | 5 | #include 6 | #include 7 | 8 | struct seat; 9 | struct keyboard; 10 | struct wlr_keyboard; 11 | 12 | void keyboard_init(struct seat *seat); 13 | void keyboard_finish(struct seat *seat); 14 | 15 | void keyboard_setup_handlers(struct keyboard *keyboard); 16 | void keyboard_set_numlock(struct wlr_keyboard *keyboard); 17 | void keyboard_update_layout(struct seat *seat, xkb_layout_index_t layout); 18 | void keyboard_cancel_keybind_repeat(struct keyboard *keyboard); 19 | bool keyboard_any_modifiers_pressed(struct wlr_keyboard *keyboard); 20 | 21 | #endif /* LABWC_KEYBOARD_H */ 22 | -------------------------------------------------------------------------------- /include/config/session.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SESSION_H 3 | #define LABWC_SESSION_H 4 | 5 | /** 6 | * session_environment_init - set enrivonment variables based on = 7 | * @dir: path to config directory 8 | * pairs in `${XDG_CONFIG_DIRS:-/etc/xdg}/lawbc/environment` with user override 9 | * in `${XDG_CONFIG_HOME:-$HOME/.config}` 10 | */ 11 | void session_environment_init(const char *dir); 12 | 13 | /** 14 | * session_autostart_init - run autostart file as shell script 15 | * @dir: path to config directory 16 | * Note: Same as `sh ~/.config/labwc/autostart` (or equivalent XDG config dir) 17 | */ 18 | void session_autostart_init(const char *dir); 19 | 20 | #endif /* LABWC_SESSION_H */ 21 | -------------------------------------------------------------------------------- /include/session-lock.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SESSION_LOCK_H 3 | #define LABWC_SESSION_LOCK_H 4 | 5 | #include 6 | 7 | struct output; 8 | struct server; 9 | 10 | struct session_lock { 11 | struct wlr_session_lock_v1 *lock; 12 | struct wlr_surface *focused; 13 | bool abandoned; 14 | 15 | struct wl_list session_lock_outputs; 16 | 17 | struct wl_listener new_surface; 18 | struct wl_listener unlock; 19 | struct wl_listener destroy; 20 | }; 21 | 22 | void session_lock_init(struct server *server); 23 | void session_lock_output_create(struct session_lock *lock, struct output *output); 24 | void session_lock_update_for_layout_change(void); 25 | 26 | #endif /* LABWC_SESSION_LOCK_H */ 27 | -------------------------------------------------------------------------------- /src/common/grab-file.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Read file into memory 4 | * 5 | * Copyright Johan Malm 2020 6 | */ 7 | 8 | #define _POSIX_C_SOURCE 200809L 9 | #include "common/grab-file.h" 10 | #include "common/buf.h" 11 | 12 | #include 13 | 14 | char * 15 | grab_file(const char *filename) 16 | { 17 | char *line = NULL; 18 | size_t len = 0; 19 | FILE *stream = fopen(filename, "r"); 20 | if (!stream) { 21 | return NULL; 22 | } 23 | struct buf buffer; 24 | buf_init(&buffer); 25 | while ((getline(&line, &len, stream) != -1)) { 26 | char *p = strrchr(line, '\n'); 27 | if (p) { 28 | *p = '\0'; 29 | } 30 | buf_add(&buffer, line); 31 | } 32 | free(line); 33 | fclose(stream); 34 | return buffer.buf; 35 | } 36 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | labwc_sources = files( 2 | 'action.c', 3 | 'buffer.c', 4 | 'debug.c', 5 | 'desktop.c', 6 | 'dnd.c', 7 | 'foreign.c', 8 | 'idle.c', 9 | 'interactive.c', 10 | 'layers.c', 11 | 'main.c', 12 | 'node.c', 13 | 'osd.c', 14 | 'output.c', 15 | 'regions.c', 16 | 'resistance.c', 17 | 'seat.c', 18 | 'server.c', 19 | 'session-lock.c', 20 | 'snap.c', 21 | 'theme.c', 22 | 'view.c', 23 | 'view-impl-common.c', 24 | 'window-rules.c', 25 | 'workspaces.c', 26 | 'xdg.c', 27 | 'xdg-popup.c', 28 | ) 29 | 30 | if have_xwayland 31 | labwc_sources += files( 32 | 'xwayland.c', 33 | 'xwayland-unmanaged.c', 34 | ) 35 | endif 36 | 37 | 38 | subdir('button') 39 | subdir('common') 40 | subdir('config') 41 | subdir('decorations') 42 | subdir('input') 43 | subdir('menu') 44 | subdir('ssd') 45 | -------------------------------------------------------------------------------- /include/common/scene-helpers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SCENE_HELPERS_H 3 | #define LABWC_SCENE_HELPERS_H 4 | 5 | #include 6 | 7 | struct wlr_scene_node; 8 | struct wlr_surface; 9 | struct wlr_scene_output; 10 | 11 | struct wlr_surface *lab_wlr_surface_from_node(struct wlr_scene_node *node); 12 | 13 | /** 14 | * lab_get_prev_node - return previous (sibling) node 15 | * @node: node to find the previous node from 16 | * Return NULL if previous link is list-head which means node is bottom-most 17 | */ 18 | struct wlr_scene_node *lab_wlr_scene_get_prev_node(struct wlr_scene_node *node); 19 | 20 | /* A variant of wlr_scene_output_commit() that respects wlr_output->pending */ 21 | bool lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output); 22 | 23 | #endif /* LABWC_SCENE_HELPERS_H */ 24 | -------------------------------------------------------------------------------- /src/common/nodename.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include "common/nodename.h" 5 | 6 | char * 7 | nodename(xmlNode *node, char *buf, int len) 8 | { 9 | if (!node || !node->name) { 10 | return NULL; 11 | } 12 | 13 | /* Ignore superflous 'text.' in node name */ 14 | if (node->parent && !strcmp((char *)node->name, "text")) { 15 | node = node->parent; 16 | } 17 | 18 | char *p = buf; 19 | p[--len] = 0; 20 | for (;;) { 21 | const char *name = (char *)node->name; 22 | char c; 23 | while ((c = *name++) != 0) { 24 | *p++ = tolower(c); 25 | if (!--len) { 26 | return buf; 27 | } 28 | } 29 | *p = 0; 30 | node = node->parent; 31 | if (!node || !node->name) { 32 | return buf; 33 | } 34 | *p++ = '.'; 35 | if (!--len) { 36 | return buf; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/common/mem.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #define _POSIX_C_SOURCE 200809L 3 | #include 4 | #include 5 | #include 6 | #include "common/mem.h" 7 | 8 | static void 9 | die_if_null(void *ptr) 10 | { 11 | if (!ptr) { 12 | perror("Failed to allocate memory"); 13 | exit(EXIT_FAILURE); 14 | } 15 | } 16 | 17 | void * 18 | xzalloc(size_t size) 19 | { 20 | if (!size) { 21 | return NULL; 22 | } 23 | void *ptr = calloc(1, size); 24 | die_if_null(ptr); 25 | return ptr; 26 | } 27 | 28 | void * 29 | xrealloc(void *ptr, size_t size) 30 | { 31 | if (!size) { 32 | free(ptr); 33 | return NULL; 34 | } 35 | ptr = realloc(ptr, size); 36 | die_if_null(ptr); 37 | return ptr; 38 | } 39 | 40 | char * 41 | xstrdup(const char *str) 42 | { 43 | assert(str); 44 | char *copy = strdup(str); 45 | die_if_null(copy); 46 | return copy; 47 | } 48 | -------------------------------------------------------------------------------- /include/common/list.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_LIST_H 3 | #define LABWC_LIST_H 4 | #include 5 | 6 | /** 7 | * wl_list_append() - add a new element to the end of a list 8 | * @list: list head to add it before 9 | * @elm: new element to be added (link of the containing struct to be precise) 10 | * 11 | * Note: In labwc, most lists are queues where we want to add new elements to 12 | * the end of the list. As wl_list_insert() adds elements at the front of the 13 | * list (like a stack) - without this helper-function - we have to use 14 | * wl_list_insert(list.prev, element) which is verbose and not intuitive to 15 | * anyone new to this API. 16 | */ 17 | static inline void 18 | wl_list_append(struct wl_list *list, struct wl_list *elm) 19 | { 20 | wl_list_insert(list->prev, elm); 21 | } 22 | 23 | #endif /* LABWC_LIST_H */ 24 | -------------------------------------------------------------------------------- /include/view-impl-common.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_VIEW_IMPL_COMMON_H 3 | #define LABWC_VIEW_IMPL_COMMON_H 4 | /* 5 | * Common code for view->impl functions 6 | * 7 | * Please note: only xdg-shell-toplevel-view and xwayland-view view_impl 8 | * functions should call these functions. 9 | */ 10 | 11 | struct view; 12 | 13 | void view_impl_move_to_front(struct view *view); 14 | void view_impl_move_to_back(struct view *view); 15 | void view_impl_map(struct view *view); 16 | void view_impl_unmap(struct view *view); 17 | 18 | /* 19 | * Updates view geometry at commit based on current position/size, 20 | * pending move/resize, and committed surface size. The computed 21 | * position may not match pending.x/y exactly in some cases. 22 | */ 23 | void view_impl_apply_geometry(struct view *view, int w, int h); 24 | 25 | #endif /* LABWC_VIEW_IMPL_COMMON_H */ 26 | -------------------------------------------------------------------------------- /include/common/parse-bool.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_PARSE_BOOL_H 3 | #define LABWC_PARSE_BOOL_H 4 | #include 5 | 6 | /** 7 | * parse_bool() - Parse boolean value of string. 8 | * @string: String to interpret. This check is case-insensitive. 9 | * @default_value: Default value to use if string is not a recognised boolean. 10 | * Use -1 to avoid setting a default value. 11 | * 12 | * Return: 0 for false; 1 for true; -1 for non-boolean 13 | */ 14 | int parse_bool(const char *str, int default_value); 15 | 16 | /** 17 | * set_bool() - Parse boolean text and set variable iff text is valid boolean 18 | * @string: Boolean text to interpret. 19 | * @variable: Variable to set. 20 | */ 21 | void set_bool(const char *str, bool *variable); 22 | void set_bool_as_int(const char *str, int *variable); 23 | 24 | #endif /* LABWC_PARSE_BOOL_H */ 25 | -------------------------------------------------------------------------------- /docs/meson.build: -------------------------------------------------------------------------------- 1 | scdoc = find_program('scdoc', required: get_option('man-pages')) 2 | 3 | if scdoc.found() 4 | sections = [ 5 | '.1', 6 | '-actions.5', 7 | '-config.5', 8 | '-menu.5', 9 | '-theme.5', 10 | ] 11 | foreach s : sections 12 | markdown = 'labwc' + s + '.scd' 13 | manpage = 'labwc' + s 14 | custom_target( 15 | manpage, 16 | input: markdown, 17 | output: manpage, 18 | command: scdoc, 19 | feed: true, 20 | capture: true, 21 | install: true, 22 | install_dir: join_paths(get_option('mandir'), 'man' + s.split('.')[-1]) 23 | ) 24 | endforeach 25 | endif 26 | 27 | install_data( 28 | [ 29 | 'autostart', 30 | 'environment', 31 | 'menu.xml', 32 | 'README', 33 | 'themerc', 34 | 'rc.xml', 35 | 'rc.xml.all' 36 | ], 37 | install_dir: get_option('datadir') / 'doc' / meson.project_name() 38 | ) 39 | -------------------------------------------------------------------------------- /include/workspaces.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_WORKSPACES_H 3 | #define LABWC_WORKSPACES_H 4 | 5 | #include 6 | #include 7 | 8 | struct seat; 9 | struct server; 10 | struct wlr_scene_tree; 11 | 12 | /* Double use: as config in config/rcxml.c and as instance in workspaces.c */ 13 | struct workspace { 14 | struct wl_list link; /* 15 | * struct server.workspaces 16 | * struct rcxml.workspace_config.workspaces 17 | */ 18 | struct server *server; 19 | 20 | char *name; 21 | struct wlr_scene_tree *tree; 22 | }; 23 | 24 | void workspaces_init(struct server *server); 25 | void workspaces_switch_to(struct workspace *target, bool update_focus); 26 | void workspaces_destroy(struct server *server); 27 | void workspaces_osd_hide(struct seat *seat); 28 | struct workspace *workspaces_find(struct workspace *anchor, const char *name, 29 | bool wrap); 30 | 31 | #endif /* LABWC_WORKSPACES_H */ 32 | -------------------------------------------------------------------------------- /scripts/check: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | file= 4 | 5 | usage_message="Usage: check [OPTIONS] 6 | OPTIONS: 7 | --file= Specify file to check. If none specified, all 8 | files in src/ and include/ will be checked. 9 | " 10 | 11 | run_checkpatch() { 12 | nice scripts/checkpatch.pl --terse --no-tree --strict --file "$1" 13 | return $? 14 | } 15 | 16 | run_checks () { 17 | if [ ! -z "$file" ]; then 18 | run_checkpatch "${file}" 19 | return $? 20 | fi 21 | 22 | find src/ include/ \( -name "*.c" -o -name "*.h" \) -type f | 23 | { 24 | errors=0 25 | while IFS= read -r file; do 26 | run_checkpatch "$file" || errors=1 27 | done 28 | return ${errors} 29 | } 30 | } 31 | 32 | main () { 33 | for arg 34 | do 35 | opt=${arg%%=*} 36 | var=${arg#*=} 37 | case "$opt" in 38 | --file) 39 | file="$var" ;; 40 | -h|--help) 41 | printf '%b' "$usage_message"; exit 1 ;; 42 | *) 43 | printf '%b\n' "warn: unknown option $opt" >&2 ;; 44 | esac 45 | done 46 | 47 | run_checks 48 | } 49 | 50 | main "$@" 51 | -------------------------------------------------------------------------------- /include/common/string-helpers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_STRING_HELPERS_H 3 | #define LABWC_STRING_HELPERS_H 4 | 5 | /** 6 | * string_strip - strip white space left and right 7 | * Note: this function does a left skip, so the returning pointer cannot be 8 | * used to free any allocated memory 9 | */ 10 | char *string_strip(char *s); 11 | 12 | /** 13 | * string_truncate_at_pattern - remove pattern and everything after it 14 | * @buf: pointer to buffer 15 | * @pattern: string to remove 16 | */ 17 | void string_truncate_at_pattern(char *buf, const char *pattern); 18 | 19 | /** 20 | * strdup_printf - allocate and write to buffer in printf format 21 | * @fmt: printf-style format. 22 | * 23 | * Similar to the standard C sprintf() function but safer as it calculates the 24 | * maximum space required and allocates memory to hold the output. 25 | * The user must free the returned string. 26 | * Returns NULL on error. 27 | */ 28 | char *strdup_printf(const char *fmt, ...); 29 | 30 | #endif /* LABWC_STRING_HELPERS_H */ 31 | -------------------------------------------------------------------------------- /include/layers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_LAYERS_H 3 | #define LABWC_LAYERS_H 4 | #include 5 | #include 6 | 7 | struct server; 8 | struct output; 9 | 10 | struct lab_layer_surface { 11 | struct wlr_scene_layer_surface_v1 *scene_layer_surface; 12 | struct server *server; 13 | 14 | bool mapped; 15 | 16 | struct wl_listener map; 17 | struct wl_listener unmap; 18 | struct wl_listener surface_commit; 19 | struct wl_listener output_destroy; 20 | struct wl_listener node_destroy; 21 | struct wl_listener new_popup; 22 | }; 23 | 24 | struct lab_layer_popup { 25 | struct wlr_xdg_popup *wlr_popup; 26 | struct wlr_scene_tree *scene_tree; 27 | 28 | /* To simplify moving popup nodes from the bottom to the top layer */ 29 | struct wlr_box output_toplevel_sx_box; 30 | 31 | struct wl_listener destroy; 32 | struct wl_listener new_popup; 33 | }; 34 | 35 | void layers_init(struct server *server); 36 | 37 | void layers_arrange(struct output *output); 38 | 39 | #endif /* LABWC_LAYERS_H */ 40 | -------------------------------------------------------------------------------- /include/common/buf.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | /* 3 | * Very simple C string buffer implementation 4 | * 5 | * Copyright Johan Malm 2020 6 | */ 7 | 8 | #ifndef LABWC_BUF_H 9 | #define LABWC_BUF_H 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | struct buf { 16 | char *buf; 17 | int alloc; 18 | int len; 19 | }; 20 | 21 | /** 22 | * buf_expand_tilde - expand ~ in buffer 23 | * @s: buffer 24 | */ 25 | void buf_expand_tilde(struct buf *s); 26 | 27 | /** 28 | * buf_expand_shell_variables - expand $foo and ${foo} in buffer 29 | * @s: buffer 30 | * Note: $$ is not handled 31 | */ 32 | void buf_expand_shell_variables(struct buf *s); 33 | 34 | /** 35 | * buf_init - allocate NULL-terminated C string buffer 36 | * @s: buffer 37 | * Note: use free(s->buf) to free it 38 | */ 39 | void buf_init(struct buf *s); 40 | 41 | /** 42 | * buf_add - add data to C string buffer 43 | * @s: buffer 44 | * @data: data to be added 45 | */ 46 | void buf_add(struct buf *s, const char *data); 47 | 48 | #endif /* LABWC_BUF_H */ 49 | -------------------------------------------------------------------------------- /include/config/libinput.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_LIBINPUT_H 3 | #define LABWC_LIBINPUT_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | enum device_type { 10 | DEFAULT_DEVICE, 11 | TOUCH_DEVICE, 12 | NON_TOUCH_DEVICE, 13 | }; 14 | 15 | struct libinput_category { 16 | enum device_type type; 17 | char *name; 18 | struct wl_list link; 19 | float pointer_speed; 20 | int natural_scroll; 21 | int left_handed; 22 | enum libinput_config_tap_state tap; 23 | enum libinput_config_tap_button_map tap_button_map; 24 | int tap_and_drag; /* -1 or libinput_config_drag_state */ 25 | int drag_lock; /* -1 or libinput_config_drag_lock_state */ 26 | int accel_profile; /* -1 or libinput_config_accel_profile */ 27 | int middle_emu; /* -1 or libinput_config_middle_emulation_state */ 28 | int dwt; /* -1 or libinput_config_dwt_state */ 29 | }; 30 | 31 | enum device_type get_device_type(const char *s); 32 | struct libinput_category *libinput_category_create(void); 33 | struct libinput_category *libinput_category_get_default(void); 34 | 35 | #endif /* LABWC_LIBINPUT_H */ 36 | -------------------------------------------------------------------------------- /include/window-rules.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_WINDOW_RULES_H 3 | #define LABWC_WINDOW_RULES_H 4 | 5 | enum window_rule_event { 6 | LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP = 0, 7 | }; 8 | 9 | enum property { 10 | LAB_PROP_UNSPECIFIED = 0, 11 | LAB_PROP_UNSET, 12 | LAB_PROP_FALSE, 13 | LAB_PROP_TRUE, 14 | }; 15 | 16 | /* 17 | * 'identifier' represents: 18 | * - 'app_id' for native Wayland windows 19 | * - 'WM_CLASS' for XWayland clients 20 | */ 21 | struct window_rule { 22 | char *identifier; 23 | char *title; 24 | bool match_once; 25 | 26 | enum window_rule_event event; 27 | struct wl_list actions; 28 | 29 | enum property server_decoration; 30 | enum property skip_taskbar; 31 | enum property skip_window_switcher; 32 | enum property ignore_focus_request; 33 | enum property fixed_position; 34 | 35 | struct wl_list link; /* struct rcxml.window_rules */ 36 | }; 37 | 38 | struct view; 39 | 40 | void window_rules_apply(struct view *view, enum window_rule_event event); 41 | enum property window_rules_get_property(struct view *view, const char *property); 42 | 43 | #endif /* LABWC_WINDOW_RULES_H */ 44 | -------------------------------------------------------------------------------- /src/common/parse-bool.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include 5 | #include "common/parse-bool.h" 6 | 7 | int 8 | parse_bool(const char *str, int default_value) 9 | { 10 | if (!str) { 11 | goto error_not_a_boolean; 12 | } else if (!strcasecmp(str, "yes")) { 13 | return true; 14 | } else if (!strcasecmp(str, "true")) { 15 | return true; 16 | } else if (!strcasecmp(str, "on")) { 17 | return true; 18 | } else if (!strcasecmp(str, "no")) { 19 | return false; 20 | } else if (!strcasecmp(str, "false")) { 21 | return false; 22 | } else if (!strcasecmp(str, "off")) { 23 | return false; 24 | } 25 | error_not_a_boolean: 26 | wlr_log(WLR_ERROR, "(%s) is not a boolean value", str); 27 | return default_value; 28 | } 29 | 30 | void 31 | set_bool(const char *str, bool *variable) 32 | { 33 | int ret = parse_bool(str, -1); 34 | if (ret < 0) { 35 | return; 36 | } 37 | *variable = ret; 38 | } 39 | 40 | void 41 | set_bool_as_int(const char *str, int *variable) 42 | { 43 | int ret = parse_bool(str, -1); 44 | if (ret < 0) { 45 | return; 46 | } 47 | *variable = ret; 48 | } 49 | -------------------------------------------------------------------------------- /docs/rc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 10 12 | 13 | 14 | 15 | 16 | 8 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /include/input/key-state.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_KEY_STATE_H 3 | #define LABWC_KEY_STATE_H 4 | 5 | #include 6 | #include 7 | 8 | /* 9 | * All keycodes in these functions are (Linux) libinput evdev scancodes which is 10 | * what 'wlr_keyboard' uses (e.g. 'seat->keyboard_group->keyboard->keycodes'). 11 | * Note: These keycodes are different to XKB scancodes by a value of 8. 12 | */ 13 | 14 | /** 15 | * key_state_pressed_sent_keycodes - generate array of pressed+sent keys 16 | * Note: The array is generated by subtracting any bound keys from _all_ pressed 17 | * keys (because bound keys were not forwarded to clients). 18 | */ 19 | uint32_t *key_state_pressed_sent_keycodes(void); 20 | int key_state_nr_pressed_sent_keycodes(void); 21 | 22 | void key_state_set_pressed(uint32_t keycode, bool is_pressed, bool is_modifier); 23 | void key_state_store_pressed_key_as_bound(uint32_t keycode); 24 | bool key_state_corresponding_press_event_was_bound(uint32_t keycode); 25 | void key_state_bound_key_remove(uint32_t keycode); 26 | int key_state_nr_bound_keys(void); 27 | int key_state_nr_pressed_keys(void); 28 | 29 | #endif /* LABWC_KEY_STATE_H */ 30 | -------------------------------------------------------------------------------- /src/common/fd_util.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #define _POSIX_C_SOURCE 200809L 3 | 4 | #include 5 | #include 6 | 7 | #include "common/fd_util.h" 8 | 9 | static struct rlimit original_nofile_rlimit = {0}; 10 | 11 | void 12 | increase_nofile_limit(void) 13 | { 14 | if (getrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { 15 | wlr_log_errno(WLR_ERROR, 16 | "Failed to bump max open files limit: getrlimit(NOFILE) failed"); 17 | return; 18 | } 19 | 20 | struct rlimit new_rlimit = original_nofile_rlimit; 21 | new_rlimit.rlim_cur = new_rlimit.rlim_max; 22 | if (setrlimit(RLIMIT_NOFILE, &new_rlimit) != 0) { 23 | wlr_log_errno(WLR_ERROR, 24 | "Failed to bump max open files limit: setrlimit(NOFILE) failed"); 25 | 26 | wlr_log(WLR_INFO, "Running with %d max open files", 27 | (int)original_nofile_rlimit.rlim_cur); 28 | } 29 | } 30 | 31 | void 32 | restore_nofile_limit(void) 33 | { 34 | if (original_nofile_rlimit.rlim_cur == 0) { 35 | return; 36 | } 37 | 38 | if (setrlimit(RLIMIT_NOFILE, &original_nofile_rlimit) != 0) { 39 | wlr_log_errno(WLR_ERROR, 40 | "Failed to restore max open files limit: setrlimit(NOFILE) failed"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /include/config/keybind.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_KEYBIND_H 3 | #define LABWC_KEYBIND_H 4 | 5 | #include 6 | #include 7 | 8 | #define MAX_KEYSYMS 32 9 | #define MAX_KEYCODES 16 10 | 11 | struct server; 12 | 13 | struct keybind { 14 | uint32_t modifiers; 15 | xkb_keysym_t *keysyms; 16 | size_t keysyms_len; 17 | bool use_syms_only; 18 | xkb_keycode_t keycodes[MAX_KEYCODES]; 19 | size_t keycodes_len; 20 | int keycodes_layout; 21 | struct wl_list actions; /* struct action.link */ 22 | struct wl_list link; /* struct rcxml.keybinds */ 23 | }; 24 | 25 | /** 26 | * keybind_create - parse keybind and add to linked list 27 | * @keybind: key combination 28 | */ 29 | struct keybind *keybind_create(const char *keybind); 30 | 31 | /** 32 | * parse_modifier - parse a string containing a single modifier name (e.g. "S") 33 | * into the represented modifier value. returns 0 for invalid modifier names. 34 | * @symname: modifier name 35 | */ 36 | uint32_t parse_modifier(const char *symname); 37 | 38 | bool keybind_the_same(struct keybind *a, struct keybind *b); 39 | 40 | void keybind_update_keycodes(struct server *server); 41 | #endif /* LABWC_KEYBIND_H */ 42 | -------------------------------------------------------------------------------- /src/common/string-helpers.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "common/mem.h" 7 | #include "common/string-helpers.h" 8 | 9 | static void 10 | rtrim(char **s) 11 | { 12 | size_t len = strlen(*s); 13 | if (!len) { 14 | return; 15 | } 16 | char *end = *s + len - 1; 17 | while (end >= *s && isspace(*end)) { 18 | end--; 19 | } 20 | *(end + 1) = '\0'; 21 | } 22 | 23 | char * 24 | string_strip(char *s) 25 | { 26 | rtrim(&s); 27 | while (isspace(*s)) { 28 | s++; 29 | } 30 | return s; 31 | } 32 | 33 | void 34 | string_truncate_at_pattern(char *buf, const char *pattern) 35 | { 36 | char *p = strstr(buf, pattern); 37 | if (!p) { 38 | return; 39 | } 40 | *p = '\0'; 41 | } 42 | 43 | char * 44 | strdup_printf(const char *fmt, ...) 45 | { 46 | size_t size = 0; 47 | char *p = NULL; 48 | va_list ap; 49 | 50 | va_start(ap, fmt); 51 | int n = vsnprintf(p, size, fmt, ap); 52 | va_end(ap); 53 | 54 | if (n < 0) { 55 | return NULL; 56 | } 57 | 58 | size = (size_t)n + 1; 59 | p = xzalloc(size); 60 | 61 | va_start(ap, fmt); 62 | n = vsnprintf(p, size, fmt, ap); 63 | va_end(ap); 64 | 65 | if (n < 0) { 66 | free(p); 67 | return NULL; 68 | } 69 | return p; 70 | } 71 | -------------------------------------------------------------------------------- /docs/autostart: -------------------------------------------------------------------------------- 1 | # Example autostart file 2 | 3 | # Set background color. 4 | swaybg -c '#113344' >/dev/null 2>&1 & 5 | 6 | # Configure output directives such as mode, position, scale and transform. 7 | # Use wlr-randr to get your output names 8 | # Example ~/.config/kanshi/config below: 9 | # profile { 10 | # output HDMI-A-1 position 1366,0 11 | # output eDP-1 position 0,0 12 | # } 13 | kanshi >/dev/null 2>&1 & 14 | 15 | # Launch a panel such as yambar or waybar. 16 | waybar >/dev/null 2>&1 & 17 | 18 | # Enable notifications. Typically GNOME/KDE application notifications go 19 | # through the org.freedesktop.Notifications D-Bus API and require a client such 20 | # as mako to function correctly. Thunderbird is an example of this. 21 | mako >/dev/null 2>&1 & 22 | 23 | # Lock screen after 5 minutes; turn off display after another 5 minutes. 24 | # 25 | # Note that in the context of idle system power management, it is *NOT* a good 26 | # idea to turn off displays by 'disabling outputs' for example by 27 | # `wlr-randr --output --off` because this re-arranges views 28 | # (since a837fef). Instead use a wlr-output-power-management client such as 29 | # https://git.sr.ht/~leon_plickat/wlopm 30 | swayidle -w \ 31 | timeout 300 'swaylock -f -c 000000' \ 32 | timeout 600 'wlopm --off \*' \ 33 | resume 'wlopm --on \*' \ 34 | before-sleep 'swaylock -f -c 000000' >/dev/null 2>&1 & 35 | -------------------------------------------------------------------------------- /protocols/meson.build: -------------------------------------------------------------------------------- 1 | wl_protocol_dir = wayland_protos.get_variable(pkgconfig: 'pkgdatadir') 2 | wayland_scanner = find_program('wayland-scanner') 3 | 4 | wayland_scanner_code = generator( 5 | wayland_scanner, 6 | output: '@BASENAME@-protocol.c', 7 | arguments: ['private-code', '@INPUT@', '@OUTPUT@'], 8 | ) 9 | 10 | wayland_scanner_server = generator( 11 | wayland_scanner, 12 | output: '@BASENAME@-protocol.h', 13 | arguments: ['server-header', '@INPUT@', '@OUTPUT@'], 14 | ) 15 | 16 | server_protocols = [ 17 | wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', 18 | wl_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', 19 | wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', 20 | wl_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', 21 | 'wlr-layer-shell-unstable-v1.xml', 22 | 'wlr-input-inhibitor-unstable-v1.xml', 23 | 'wlr-output-power-management-unstable-v1.xml', 24 | ] 25 | 26 | server_protos_src = [] 27 | server_protos_headers = [] 28 | 29 | foreach xml : server_protocols 30 | server_protos_src += wayland_scanner_code.process(xml) 31 | server_protos_headers += wayland_scanner_server.process(xml) 32 | endforeach 33 | 34 | lib_server_protos = static_library( 35 | 'server_protos', 36 | server_protos_src + server_protos_headers, 37 | dependencies: [wayland_server] 38 | ) 39 | 40 | server_protos = declare_dependency( 41 | link_with: lib_server_protos, 42 | sources: server_protos_headers, 43 | ) 44 | -------------------------------------------------------------------------------- /include/action.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_ACTION_H 3 | #define LABWC_ACTION_H 4 | 5 | #include 6 | 7 | struct view; 8 | struct server; 9 | 10 | struct action { 11 | struct wl_list link; /* 12 | * struct keybinding.actions 13 | * struct mousebinding.actions 14 | * struct menuitem.actions 15 | */ 16 | 17 | uint32_t type; /* enum action_type */ 18 | struct wl_list args; /* struct action_arg.link */ 19 | }; 20 | 21 | struct action *action_create(const char *action_name); 22 | 23 | bool action_is_valid(struct action *action); 24 | 25 | void action_arg_add_str(struct action *action, const char *key, const char *value); 26 | void action_arg_add_actionlist(struct action *action, const char *key); 27 | void action_arg_add_querylist(struct action *action, const char *key); 28 | 29 | struct wl_list *action_get_actionlist(struct action *action, const char *key); 30 | struct wl_list *action_get_querylist(struct action *action, const char *key); 31 | 32 | void action_arg_from_xml_node(struct action *action, const char *nodename, const char *content); 33 | 34 | bool actions_contain_toggle_keybinds(struct wl_list *action_list); 35 | 36 | void actions_run(struct view *activator, struct server *server, 37 | struct wl_list *actions, uint32_t resize_edges); 38 | 39 | void action_free(struct action *action); 40 | void action_list_free(struct wl_list *action_list); 41 | 42 | #endif /* LABWC_ACTION_H */ 43 | -------------------------------------------------------------------------------- /po/labwc.pot: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2023 3 | # This file is distributed under the same license as the labwc package. 4 | # FIRST AUTHOR , YEAR. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 13 | "Last-Translator: FULL NAME \n" 14 | "Language-Team: LANGUAGE \n" 15 | "Language: \n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:698 21 | msgid "Reconfigure" 22 | msgstr "" 23 | 24 | #: src/menu/menu.c:700 25 | msgid "Exit" 26 | msgstr "" 27 | 28 | #: src/menu/menu.c:716 29 | msgid "Minimize" 30 | msgstr "" 31 | 32 | #: src/menu/menu.c:718 33 | msgid "Maximize" 34 | msgstr "" 35 | 36 | #: src/menu/menu.c:720 37 | msgid "Fullscreen" 38 | msgstr "" 39 | 40 | #: src/menu/menu.c:722 41 | msgid "Decorations" 42 | msgstr "" 43 | 44 | #: src/menu/menu.c:724 45 | msgid "AlwaysOnTop" 46 | msgstr "" 47 | 48 | #: src/menu/menu.c:726 49 | msgid "ToggleOmnipresent" 50 | msgstr "" 51 | 52 | #: src/menu/menu.c:731 53 | msgid "Move left" 54 | msgstr "" 55 | 56 | #: src/menu/menu.c:738 57 | msgid "Move right" 58 | msgstr "" 59 | 60 | #: src/menu/menu.c:741 61 | msgid "Workspace" 62 | msgstr "" 63 | 64 | #: src/menu/menu.c:744 65 | msgid "Close" 66 | msgstr "" 67 | -------------------------------------------------------------------------------- /po/tr.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2022 3 | # This file is distributed under the same license as the labwc package. 4 | # Oğuz Ersen , 2022. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: labwc\n" 9 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 10 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 11 | "PO-Revision-Date: 2022-11-20 17:04+0300\n" 12 | "Last-Translator: Oğuz Ersen \n" 13 | "Language-Team: Turkish \n" 14 | "Language: tr\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | 19 | #: src/menu/menu.c:631 20 | msgid "Reconfigure" 21 | msgstr "Yeniden yapılandır" 22 | 23 | #: src/menu/menu.c:633 24 | msgid "Exit" 25 | msgstr "Çıkış" 26 | 27 | #: src/menu/menu.c:649 28 | msgid "Minimize" 29 | msgstr "Küçült" 30 | 31 | #: src/menu/menu.c:651 32 | msgid "Maximize" 33 | msgstr "Büyüt" 34 | 35 | #: src/menu/menu.c:653 36 | msgid "Fullscreen" 37 | msgstr "Tam ekran" 38 | 39 | #: src/menu/menu.c:655 40 | msgid "Decorations" 41 | msgstr "Süslemeler" 42 | 43 | #: src/menu/menu.c:657 44 | msgid "AlwaysOnTop" 45 | msgstr "Her zaman üstte" 46 | 47 | #: src/menu/menu.c:662 48 | msgid "Move left" 49 | msgstr "Sola git" 50 | 51 | #: src/menu/menu.c:667 52 | msgid "Move right" 53 | msgstr "Sağa git" 54 | 55 | #: src/menu/menu.c:672 56 | msgid "Workspace" 57 | msgstr "Çalışma alanı" 58 | 59 | #: src/menu/menu.c:675 60 | msgid "Close" 61 | msgstr "Kapat" 62 | -------------------------------------------------------------------------------- /po/sv.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2022 3 | # This file is distributed under the same license as the labwc package. 4 | # Johan Malm , 2022. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: 2022-04-30 16:50+1000\n" 13 | "Last-Translator: Johan Malm \n" 15 | "Language: sv\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:631 21 | msgid "Reconfigure" 22 | msgstr "Konfigurera om" 23 | 24 | #: src/menu/menu.c:633 25 | msgid "Exit" 26 | msgstr "Utgång" 27 | 28 | #: src/menu/menu.c:649 29 | msgid "Minimize" 30 | msgstr "Minimera" 31 | 32 | #: src/menu/menu.c:651 33 | msgid "Maximize" 34 | msgstr "Maximera" 35 | 36 | #: src/menu/menu.c:653 37 | msgid "Fullscreen" 38 | msgstr "Fullskärm" 39 | 40 | #: src/menu/menu.c:655 41 | msgid "Decorations" 42 | msgstr "Dekorationer" 43 | 44 | #: src/menu/menu.c:657 45 | msgid "AlwaysOnTop" 46 | msgstr "Alltid överst" 47 | 48 | #: src/menu/menu.c:662 49 | msgid "Move left" 50 | msgstr "Flytta till vänster" 51 | 52 | #: src/menu/menu.c:667 53 | msgid "Move right" 54 | msgstr "Flytta till höger" 55 | 56 | #: src/menu/menu.c:672 57 | msgid "Workspace" 58 | msgstr "Arbetsyta" 59 | 60 | #: src/menu/menu.c:675 61 | msgid "Close" 62 | msgstr "Stäng" 63 | -------------------------------------------------------------------------------- /po/it.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2022 3 | # This file is distributed under the same license as the labwc package. 4 | # Mick Amadio <01micko@gmail.com>, 2022. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: 2022-04-30 16:50+1000\n" 13 | "Last-Translator: Mick Amadio <01micko@gmail.com>\n" 14 | "Language-Team: Italian \n" 15 | "Language: it\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:631 21 | msgid "Reconfigure" 22 | msgstr "Riconfigurare" 23 | 24 | #: src/menu/menu.c:633 25 | msgid "Exit" 26 | msgstr "Uscita" 27 | 28 | #: src/menu/menu.c:649 29 | msgid "Minimize" 30 | msgstr "Riduci" 31 | 32 | #: src/menu/menu.c:651 33 | msgid "Maximize" 34 | msgstr "Ingrandisci" 35 | 36 | #: src/menu/menu.c:653 37 | msgid "Fullscreen" 38 | msgstr "Schermo intero" 39 | 40 | #: src/menu/menu.c:655 41 | msgid "Decorations" 42 | msgstr "Decorazioni" 43 | 44 | #: src/menu/menu.c:657 45 | msgid "AlwaysOnTop" 46 | msgstr "Sempre sopra" 47 | 48 | #: src/menu/menu.c:662 49 | msgid "Move left" 50 | msgstr "Sposta a sinistra" 51 | 52 | #: src/menu/menu.c:667 53 | msgid "Move right" 54 | msgstr "Sposta a destra" 55 | 56 | #: src/menu/menu.c:672 57 | msgid "Workspace" 58 | msgstr "Area di lavoro" 59 | 60 | #: src/menu/menu.c:675 61 | msgid "Close" 62 | msgstr "Chiudi" 63 | -------------------------------------------------------------------------------- /include/common/array.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_ARRAY_H 3 | #define LABWC_ARRAY_H 4 | #include 5 | 6 | /* 7 | * Wayland's wl_array API is a bit sparse consisting only of 8 | * - init 9 | * - release 10 | * - add 11 | * - copy 12 | * - for_each 13 | * 14 | * The purpose of this header is the gather any generic wl_array helpers we 15 | * create. 16 | * 17 | * We take the liberty of using the wl_ suffix here to make it look a bit 18 | * prettier. If Wayland extend the API in future, we will sort the clash then. 19 | */ 20 | 21 | /** 22 | * wl_array_len() - return length of wl_array 23 | * @array: wl_array for which to calculate length 24 | * Note: The pointer type might not be 'char' but this is the approach that 25 | * wl_array_for_each() takes, so we align with their style. 26 | */ 27 | static inline size_t 28 | wl_array_len(struct wl_array *array) 29 | { 30 | return array->size / sizeof(const char *); 31 | } 32 | 33 | /** 34 | * Iterates in reverse over an array. 35 | * @pos: pointer that each array element will be assigned to 36 | * @array: wl_array to iterate over 37 | */ 38 | #define wl_array_for_each_reverse(pos, array) \ 39 | for (pos = !(array)->data ? NULL \ 40 | : (void *)((const char *)(array)->data + (array)->size - sizeof(pos)); \ 41 | pos && (const char *)pos >= (const char *)(array)->data; \ 42 | (pos)--) 43 | 44 | #endif /* LABWC_ARRAY_H */ 45 | -------------------------------------------------------------------------------- /po/es.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2022 3 | # This file is distributed under the same license as the labwc package. 4 | # Mick Amadio <01micko@gmail.com>, 2022. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: 2022-04-30 16:50+1000\n" 13 | "Last-Translator: Mick Amadio <01micko@gmail.com>\n" 14 | "Language-Team: Spanish \n" 15 | "Language: es\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:631 21 | msgid "Reconfigure" 22 | msgstr "Reconfigurar" 23 | 24 | #: src/menu/menu.c:633 25 | msgid "Exit" 26 | msgstr "Salir" 27 | 28 | #: src/menu/menu.c:649 29 | msgid "Minimize" 30 | msgstr "Minimizar" 31 | 32 | #: src/menu/menu.c:651 33 | msgid "Maximize" 34 | msgstr "Maximizar" 35 | 36 | #: src/menu/menu.c:653 37 | msgid "Fullscreen" 38 | msgstr "Pantalla completa" 39 | 40 | #: src/menu/menu.c:655 41 | msgid "Decorations" 42 | msgstr "Decoraciones" 43 | 44 | #: src/menu/menu.c:657 45 | msgid "AlwaysOnTop" 46 | msgstr "Siempre encima" 47 | 48 | #: src/menu/menu.c:662 49 | msgid "Move left" 50 | msgstr "Mover a la izquierda" 51 | 52 | #: src/menu/menu.c:667 53 | msgid "Move right" 54 | msgstr "Mover a la derecha" 55 | 56 | #: src/menu/menu.c:672 57 | msgid "Workspace" 58 | msgstr "Espacio de trabajo" 59 | 60 | #: src/menu/menu.c:675 61 | msgid "Close" 62 | msgstr "Cerrar" 63 | -------------------------------------------------------------------------------- /po/de.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2022 3 | # This file is distributed under the same license as the labwc package. 4 | # Consolatis , 2022. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: 2022-04-30 16:50+1000\n" 13 | "Last-Translator: Consolatis \n" 14 | "Language-Team: German \n" 15 | "Language: de\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:631 21 | msgid "Reconfigure" 22 | msgstr "Rekonfigurieren" 23 | 24 | #: src/menu/menu.c:633 25 | msgid "Exit" 26 | msgstr "Beenden" 27 | 28 | #: src/menu/menu.c:649 29 | msgid "Minimize" 30 | msgstr "Minimieren" 31 | 32 | #: src/menu/menu.c:651 33 | msgid "Maximize" 34 | msgstr "Maximieren" 35 | 36 | #: src/menu/menu.c:653 37 | msgid "Fullscreen" 38 | msgstr "Vollbild" 39 | 40 | #: src/menu/menu.c:655 41 | msgid "Decorations" 42 | msgstr "Dekorationen" 43 | 44 | #: src/menu/menu.c:657 45 | msgid "AlwaysOnTop" 46 | msgstr "Immer im Vordergrund" 47 | 48 | #: src/menu/menu.c:662 49 | msgid "Move left" 50 | msgstr "nach links" 51 | 52 | #: src/menu/menu.c:667 53 | msgid "Move right" 54 | msgstr "nach rechts" 55 | 56 | #: src/menu/menu.c:672 57 | msgid "Workspace" 58 | msgstr "Arbeitsfläche" 59 | 60 | #: src/menu/menu.c:675 61 | msgid "Close" 62 | msgstr "Schließen" 63 | -------------------------------------------------------------------------------- /po/id.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2023 3 | # This file is distributed under the same license as the labwc package. 4 | # May Mantari , 2023. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: 2023-04-14 20:40+0700\n" 13 | "Last-Translator: May Mantari \n" 15 | "Language: id\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:631 21 | msgid "Reconfigure" 22 | msgstr "Atur Ulang" 23 | 24 | #: src/menu/menu.c:633 25 | msgid "Exit" 26 | msgstr "Keluar" 27 | 28 | #: src/menu/menu.c:649 29 | msgid "Minimize" 30 | msgstr "Sembunyikan" 31 | 32 | #: src/menu/menu.c:651 33 | msgid "Maximize" 34 | msgstr "Perluas Jendela" 35 | 36 | #: src/menu/menu.c:653 37 | msgid "Fullscreen" 38 | msgstr "Tampil Menyeluruh" 39 | 40 | #: src/menu/menu.c:655 41 | msgid "Decorations" 42 | msgstr "Dekorasi" 43 | 44 | #: src/menu/menu.c:657 45 | msgid "AlwaysOnTop" 46 | msgstr "Selalu di Muka" 47 | 48 | #: src/menu/menu.c:662 49 | msgid "Move left" 50 | msgstr "Geser ke Kiri" 51 | 52 | #: src/menu/menu.c:667 53 | msgid "Move right" 54 | msgstr "Geser ke Kanan" 55 | 56 | #: src/menu/menu.c:672 57 | msgid "Workspace" 58 | msgstr "Ruang Kerja" 59 | 60 | #: src/menu/menu.c:675 61 | msgid "Close" 62 | msgstr "Tutup" 63 | -------------------------------------------------------------------------------- /po/pl.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2023 3 | # This file is distributed under the same license as the labwc package. 4 | # Marcin Puc , 2023. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: 2023-04-24 14:45+0200\n" 13 | "Last-Translator: Marcin Puc \n" 14 | "Language-Team: Polish \n" 15 | "Language: pl\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:631 21 | msgid "Reconfigure" 22 | msgstr "Rekonfiguruj" 23 | 24 | #: src/menu/menu.c:633 25 | msgid "Exit" 26 | msgstr "Wyjdź" 27 | 28 | #: src/menu/menu.c:649 29 | msgid "Minimize" 30 | msgstr "Minimalizuj" 31 | 32 | #: src/menu/menu.c:651 33 | msgid "Maximize" 34 | msgstr "Maksymalizuj" 35 | 36 | #: src/menu/menu.c:653 37 | msgid "Fullscreen" 38 | msgstr "Pełny ekran" 39 | 40 | #: src/menu/menu.c:655 41 | msgid "Decorations" 42 | msgstr "Dekoracje" 43 | 44 | #: src/menu/menu.c:657 45 | msgid "AlwaysOnTop" 46 | msgstr "Zawsze na wierzchu" 47 | 48 | #: src/menu/menu.c:662 49 | msgid "Move left" 50 | msgstr "Przenieś w lewo" 51 | 52 | #: src/menu/menu.c:667 53 | msgid "Move right" 54 | msgstr "Przenieś w prawo" 55 | 56 | #: src/menu/menu.c:672 57 | msgid "Workspace" 58 | msgstr "Przestrzeń robocza" 59 | 60 | #: src/menu/menu.c:675 61 | msgid "Close" 62 | msgstr "Zamknij" 63 | -------------------------------------------------------------------------------- /po/ru.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2023 3 | # This file is distributed under the same license as the labwc package. 4 | # Aleksey Samoilov , 2023. 5 | # 6 | #, fuzzy 7 | msgid "" 8 | msgstr "" 9 | "Project-Id-Version: labwc\n" 10 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 11 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 12 | "PO-Revision-Date: 2023-04-14 15:18+1000\n" 13 | "Last-Translator: Aleksey Samoilov \n" 14 | "Language-Team: Russian \n" 15 | "Language: ru\n" 16 | "MIME-Version: 1.0\n" 17 | "Content-Type: text/plain; charset=UTF-8\n" 18 | "Content-Transfer-Encoding: 8bit\n" 19 | 20 | #: src/menu/menu.c:631 21 | msgid "Reconfigure" 22 | msgstr "Перенастроить" 23 | 24 | #: src/menu/menu.c:633 25 | msgid "Exit" 26 | msgstr "Выход" 27 | 28 | #: src/menu/menu.c:649 29 | msgid "Minimize" 30 | msgstr "Свернуть" 31 | 32 | #: src/menu/menu.c:651 33 | msgid "Maximize" 34 | msgstr "Распахнуть" 35 | 36 | #: src/menu/menu.c:653 37 | msgid "Fullscreen" 38 | msgstr "На весь экран" 39 | 40 | #: src/menu/menu.c:655 41 | msgid "Decorations" 42 | msgstr "Декорации" 43 | 44 | #: src/menu/menu.c:657 45 | msgid "AlwaysOnTop" 46 | msgstr "Всегда на переднем плане" 47 | 48 | #: src/menu/menu.c:662 49 | msgid "Move left" 50 | msgstr "Переместить влево" 51 | 52 | #: src/menu/menu.c:667 53 | msgid "Move right" 54 | msgstr "Переместить вправо" 55 | 56 | #: src/menu/menu.c:672 57 | msgid "Workspace" 58 | msgstr "Рабочее пространство" 59 | 60 | #: src/menu/menu.c:675 61 | msgid "Close" 62 | msgstr "Закрыть" 63 | -------------------------------------------------------------------------------- /docs/labwc.1.scd: -------------------------------------------------------------------------------- 1 | labwc(1) 2 | 3 | # NAME 4 | 5 | labwc - a wayland stacking compositor 6 | 7 | # SYNOPSIS 8 | 9 | *labwc*  [options...] 10 | 11 | # DESCRIPTION 12 | 13 | Labwc is a wlroots-based stacking compositor for wayland. 14 | 15 | It is light-weight and independent with a focus on simply stacking windows 16 | well and rendering some window decorations. Where practicable it uses clients 17 | for wall-paper, panels, screenshots and so on. 18 | 19 | The compositor will exit or reload its configuration upon receiving SIGTERM 20 | and SIGHUP respectively. For example: 21 | 22 | ``` 23 | kill -s $LABWC_PID 24 | killall -s labwc 25 | ``` 26 | 27 | Each running instance of labwc sets the environment variable `LABWC_PID` to 28 | its PID. This is useful for sending signals to a specific instance and is what 29 | the `--exit` and `--reconfigure` options use. 30 | 31 | # OPTIONS 32 | 33 | *-c, --config* 34 | Specify a config file with path 35 | 36 | *-C, --config-dir* 37 | Specify a config directory 38 | 39 | *-d, --debug* 40 | Enable full logging, including debug information 41 | 42 | *-e, --exit* 43 | Exit the compositor 44 | 45 | *-h, --help* 46 | Show help message and quit 47 | 48 | *-r, --reconfigure* 49 | Reload the compositor configuration 50 | 51 | *-s, --startup* 52 | Run command on startup 53 | 54 | *-v, --version* 55 | Show the version number and quit 56 | 57 | *-V, --verbose* 58 | Enable more verbose logging 59 | 60 | # SEE ALSO 61 | 62 | labwc-config(5), labwc-theme(5), labwc-actions(5) 63 | -------------------------------------------------------------------------------- /include/common/font.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_FONT_H 3 | #define LABWC_FONT_H 4 | 5 | struct lab_data_buffer; 6 | 7 | enum font_slant { 8 | FONT_SLANT_NORMAL = 0, 9 | FONT_SLANT_ITALIC 10 | }; 11 | 12 | enum font_weight { 13 | FONT_WEIGHT_NORMAL = 0, 14 | FONT_WEIGHT_BOLD 15 | }; 16 | 17 | struct font { 18 | char *name; 19 | int size; 20 | enum font_slant slant; 21 | enum font_weight weight; 22 | }; 23 | 24 | struct _PangoFontDescription *font_to_pango_desc(struct font *font); 25 | 26 | /** 27 | * font_height - get font vertical extents 28 | * @font: description of font including family name and size 29 | */ 30 | int font_height(struct font *font); 31 | 32 | /** 33 | * font_width - get font horizontal extents 34 | * @font: description of font including family name and size 35 | */ 36 | int font_width(struct font *font, const char *string); 37 | 38 | /** 39 | * font_buffer_create - Create ARGB8888 lab_data_buffer using pango 40 | * @buffer: buffer pointer 41 | * @max_width: max allowable width; will be ellipsized if longer 42 | * @text: text to be generated as texture 43 | * @font: font description 44 | * @color: foreground color in rgba format 45 | * @arrow: arrow (utf8) character to show or NULL for none 46 | */ 47 | void font_buffer_create(struct lab_data_buffer **buffer, int max_width, 48 | const char *text, struct font *font, float *color, const char *arrow, 49 | double scale); 50 | 51 | /** 52 | * font_finish - free some font related resources 53 | * Note: use on exit 54 | */ 55 | void font_finish(void); 56 | 57 | #endif /* LABWC_FONT_H */ 58 | -------------------------------------------------------------------------------- /src/common/spawn.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #define _POSIX_C_SOURCE 200809L 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "common/spawn.h" 13 | #include "common/fd_util.h" 14 | 15 | void 16 | spawn_async_no_shell(char const *command) 17 | { 18 | GError *err = NULL; 19 | gchar **argv = NULL; 20 | 21 | assert(command); 22 | 23 | /* Use glib's shell-parse to mimic Openbox's behaviour */ 24 | g_shell_parse_argv((gchar *)command, NULL, &argv, &err); 25 | if (err) { 26 | g_message("%s", err->message); 27 | g_error_free(err); 28 | return; 29 | } 30 | 31 | /* 32 | * Avoid zombie processes by using a double-fork, whereby the 33 | * grandchild becomes orphaned & the responsibility of the OS. 34 | */ 35 | pid_t child = 0, grandchild = 0; 36 | 37 | child = fork(); 38 | switch (child) { 39 | case -1: 40 | wlr_log(WLR_ERROR, "unable to fork()"); 41 | goto out; 42 | case 0: 43 | restore_nofile_limit(); 44 | 45 | setsid(); 46 | sigset_t set; 47 | sigemptyset(&set); 48 | sigprocmask(SIG_SETMASK, &set, NULL); 49 | /* Restore ignored signals */ 50 | signal(SIGPIPE, SIG_DFL); 51 | grandchild = fork(); 52 | if (grandchild == 0) { 53 | execvp(argv[0], argv); 54 | _exit(0); 55 | } else if (grandchild < 0) { 56 | wlr_log(WLR_ERROR, "unable to fork()"); 57 | } 58 | _exit(0); 59 | default: 60 | break; 61 | } 62 | waitpid(child, NULL, 0); 63 | out: 64 | g_strfreev(argv); 65 | } 66 | 67 | -------------------------------------------------------------------------------- /include/config/mousebind.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_MOUSEBIND_H 3 | #define LABWC_MOUSEBIND_H 4 | 5 | #include 6 | #include "ssd.h" 7 | #include "config/keybind.h" 8 | 9 | enum mouse_event { 10 | MOUSE_ACTION_NONE = 0, 11 | MOUSE_ACTION_DOUBLECLICK, 12 | MOUSE_ACTION_CLICK, 13 | MOUSE_ACTION_PRESS, 14 | MOUSE_ACTION_RELEASE, 15 | MOUSE_ACTION_DRAG, 16 | MOUSE_ACTION_SCROLL, 17 | }; 18 | 19 | enum direction { 20 | LAB_DIRECTION_INVALID = 0, 21 | LAB_DIRECTION_LEFT, 22 | LAB_DIRECTION_RIGHT, 23 | LAB_DIRECTION_UP, 24 | LAB_DIRECTION_DOWN, 25 | }; 26 | 27 | struct mousebind { 28 | enum ssd_part_type context; 29 | 30 | /* ex: BTN_LEFT, BTN_RIGHT from linux/input_event_codes.h */ 31 | uint32_t button; 32 | 33 | /* scroll direction; considered instead of button for scroll events */ 34 | enum direction direction; 35 | 36 | /* ex: WLR_MODIFIER_SHIFT | WLR_MODIFIER_LOGO */ 37 | uint32_t modifiers; 38 | 39 | /* ex: doubleclick, press, drag */ 40 | enum mouse_event mouse_event; 41 | struct wl_list actions; /* struct action.link */ 42 | 43 | struct wl_list link; /* struct rcxml.mousebinds */ 44 | bool pressed_in_context; /* used in click events */ 45 | }; 46 | 47 | enum mouse_event mousebind_event_from_str(const char *str); 48 | uint32_t mousebind_button_from_str(const char *str, uint32_t *modifiers); 49 | enum direction mousebind_direction_from_str(const char *str, uint32_t *modifiers); 50 | struct mousebind *mousebind_create(const char *context); 51 | bool mousebind_the_same(struct mousebind *a, struct mousebind *b); 52 | 53 | #endif /* LABWC_MOUSEBIND_H */ 54 | -------------------------------------------------------------------------------- /src/config/libinput.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | 5 | #include "common/mem.h" 6 | #include "config/libinput.h" 7 | #include "config/rcxml.h" 8 | 9 | static void 10 | libinput_category_init(struct libinput_category *l) 11 | { 12 | l->type = DEFAULT_DEVICE; 13 | l->name = NULL; 14 | l->pointer_speed = -2; 15 | l->natural_scroll = -1; 16 | l->left_handed = -1; 17 | l->tap = LIBINPUT_CONFIG_TAP_ENABLED; 18 | l->tap_button_map = LIBINPUT_CONFIG_TAP_MAP_LRM; 19 | l->tap_and_drag = -1; 20 | l->drag_lock = -1; 21 | l->accel_profile = -1; 22 | l->middle_emu = -1; 23 | l->dwt = -1; 24 | } 25 | 26 | enum device_type 27 | get_device_type(const char *s) 28 | { 29 | if (!s) { 30 | return DEFAULT_DEVICE; 31 | } 32 | if (!strcasecmp(s, "touch")) { 33 | return TOUCH_DEVICE; 34 | } 35 | if (!strcasecmp(s, "non-touch")) { 36 | return NON_TOUCH_DEVICE; 37 | } 38 | return DEFAULT_DEVICE; 39 | } 40 | 41 | struct libinput_category * 42 | libinput_category_create(void) 43 | { 44 | struct libinput_category *l = znew(*l); 45 | libinput_category_init(l); 46 | wl_list_insert(&rc.libinput_categories, &l->link); 47 | return l; 48 | } 49 | 50 | /* 51 | * The default category is the first one with type == DEFAULT_DEVICE and 52 | * no name. After rcxml_read(), a default category always exists. 53 | */ 54 | struct libinput_category * 55 | libinput_category_get_default(void) 56 | { 57 | struct libinput_category *l; 58 | wl_list_for_each(l, &rc.libinput_categories, link) { 59 | if (l->type == DEFAULT_DEVICE && !l->name) { 60 | return l; 61 | } 62 | } 63 | return NULL; 64 | } 65 | -------------------------------------------------------------------------------- /po/ka.po: -------------------------------------------------------------------------------- 1 | # Labwc pot file 2 | # Copyright (C) 2023 3 | # This file is distributed under the same license as the labwc package. 4 | # Temuri Doghonadze , 2023. 5 | # 6 | msgid "" 7 | msgstr "" 8 | "Project-Id-Version: labwc\n" 9 | "Report-Msgid-Bugs-To: https://github.com/labwc/labwc/issues\n" 10 | "POT-Creation-Date: 2023-01-02 11:22+1000\n" 11 | "PO-Revision-Date: 2023-02-22 09:43+0100\n" 12 | "Last-Translator: Temuri Doghonadze \n" 13 | "Language-Team: Georgian \n" 14 | "Language: ka\n" 15 | "MIME-Version: 1.0\n" 16 | "Content-Type: text/plain; charset=UTF-8\n" 17 | "Content-Transfer-Encoding: 8bit\n" 18 | "Plural-Forms: nplurals=2; plural=(n != 1);\n" 19 | "X-Generator: Poedit 3.2.2\n" 20 | 21 | #: src/menu/menu.c:631 22 | msgid "Reconfigure" 23 | msgstr "თავიდან მორგება" 24 | 25 | #: src/menu/menu.c:633 26 | msgid "Exit" 27 | msgstr "გასვლა" 28 | 29 | #: src/menu/menu.c:649 30 | msgid "Minimize" 31 | msgstr "ჩაკეცვა" 32 | 33 | #: src/menu/menu.c:651 34 | msgid "Maximize" 35 | msgstr "გადიდება" 36 | 37 | #: src/menu/menu.c:653 38 | msgid "Fullscreen" 39 | msgstr "მთელ ეკრანზე" 40 | 41 | #: src/menu/menu.c:655 42 | msgid "Decorations" 43 | msgstr "დეკორაციები" 44 | 45 | #: src/menu/menu.c:657 46 | msgid "AlwaysOnTop" 47 | msgstr "ყოველთვისყველაზეზემოდან" 48 | 49 | #: src/menu/menu.c:662 50 | msgid "Move left" 51 | msgstr "მარცხნივ გაწევა" 52 | 53 | #: src/menu/menu.c:667 54 | msgid "Move right" 55 | msgstr "მარჯვნივ გაწევა" 56 | 57 | #: src/menu/menu.c:672 58 | msgid "Workspace" 59 | msgstr "სამუშაო ადგილი" 60 | 61 | #: src/menu/menu.c:675 62 | msgid "Close" 63 | msgstr "დახურვა" 64 | -------------------------------------------------------------------------------- /include/common/graphic-helpers.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_GRAPHIC_HELPERS_H 3 | #define LABWC_GRAPHIC_HELPERS_H 4 | 5 | #include 6 | #include 7 | 8 | struct wlr_scene_tree; 9 | struct wlr_scene_rect; 10 | struct wlr_fbox; 11 | 12 | struct multi_rect { 13 | struct wlr_scene_tree *tree; 14 | int line_width; /* read-only */ 15 | 16 | /* Private */ 17 | struct wlr_scene_rect *top[3]; 18 | struct wlr_scene_rect *bottom[3]; 19 | struct wlr_scene_rect *left[3]; 20 | struct wlr_scene_rect *right[3]; 21 | struct wl_listener destroy; 22 | }; 23 | 24 | /** 25 | * Create a new multi_rect. 26 | * A multi_rect consists of 3 nested rectangular outlines. 27 | * Each of the rectangular outlines is using the same @line_width 28 | * but its own color based on the @colors argument. 29 | * 30 | * The multi-rect can be positioned by positioning multi_rect->tree->node. 31 | * 32 | * It can be destroyed by destroying its tree node (or one of its 33 | * parent nodes). Once the tree node has been destroyed the struct 34 | * will be free'd automatically. 35 | */ 36 | struct multi_rect *multi_rect_create(struct wlr_scene_tree *parent, 37 | float *colors[3], int line_width); 38 | 39 | void multi_rect_set_size(struct multi_rect *rect, int width, int height); 40 | 41 | /** 42 | * Sets the cairo color. 43 | * Splits a float[4] single color array into its own arguments 44 | */ 45 | void set_cairo_color(cairo_t *cairo, float *color); 46 | 47 | /* Draws a border with a specified line width */ 48 | void draw_cairo_border(cairo_t *cairo, struct wlr_fbox fbox, double line_width); 49 | 50 | #endif /* LABWC_GRAPHIC_HELPERS_H */ 51 | -------------------------------------------------------------------------------- /include/common/mem.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_MEM_H 3 | #define LABWC_MEM_H 4 | 5 | #include 6 | 7 | /* 8 | * As defined in busybox, weston, etc. 9 | * Allocates zero-filled memory; calls exit() on error. 10 | * Returns NULL only if (size == 0). 11 | */ 12 | void *xzalloc(size_t size); 13 | 14 | /* 15 | * Type-safe macros in the style of C++ new/new[]. 16 | * Both allocate zero-filled memory for object(s) the same size as 17 | * , which may be either a type name or value expression. 18 | * 19 | * znew() allocates space for one object. 20 | * znew_n() allocates space for an array of objects. 21 | * 22 | * Examples: 23 | * struct wlr_box *box = znew(*box); 24 | * char *buf = znew_n(char, 80); 25 | */ 26 | #define znew(expr) ((__typeof__(expr) *)xzalloc(sizeof(expr))) 27 | #define znew_n(expr, n) ((__typeof__(expr) *)xzalloc((n) * sizeof(expr))) 28 | 29 | /* 30 | * As defined in FreeBSD. 31 | * Like realloc(), but calls exit() on error. 32 | * Returns NULL only if (size == 0). 33 | * Does NOT zero-fill memory. 34 | */ 35 | void *xrealloc(void *ptr, size_t size); 36 | 37 | /* malloc() is a specific case of realloc() */ 38 | #define xmalloc(size) xrealloc(NULL, (size)) 39 | 40 | /* 41 | * As defined in FreeBSD. 42 | * Allocates a copy of ; calls exit() on error. 43 | * Requires (str != NULL) and never returns NULL. 44 | */ 45 | char *xstrdup(const char *str); 46 | 47 | /* 48 | * Frees memory pointed to by and sets to NULL. 49 | * Does nothing if is already NULL. 50 | */ 51 | #define zfree(ptr) do { \ 52 | free(ptr); (ptr) = NULL; \ 53 | } while (0) 54 | 55 | #endif /* LABWC_MEM_H */ 56 | -------------------------------------------------------------------------------- /include/common/macros.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_MACROS_H 3 | #define LABWC_MACROS_H 4 | 5 | /** 6 | * ARRAY_SIZE() - Get the number of elements in array. 7 | * @arr: array to be sized 8 | * 9 | * This does not work on pointers. 10 | * 11 | * Recent versions of GCC and clang support -Werror=sizeof-pointer-div 12 | * and thus avoids using constructs such as: 13 | * 14 | * #define same_type(a, b) (__builtin_types_compatible_p(typeof(a), typeof(b)) == 1) 15 | * #define ARRAY_SIZE(a) ({ static_assert(!same_type(a, &(a)[0])); sizeof(a) / sizeof(a[0]); }) 16 | */ 17 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 18 | 19 | /** 20 | * CONNECT_SIGNAL() - Connect a signal handler function to a wl_signal. 21 | * 22 | * @param src Signal emitter (struct containing wl_signal) 23 | * @param dest Signal receiver (struct containing wl_listener) 24 | * @param name Signal name 25 | * 26 | * This assumes that the common pattern is followed where: 27 | * - the wl_signal is (*src).events. 28 | * - the wl_listener is (*dest). 29 | * - the signal handler function is named handle_ 30 | */ 31 | #define CONNECT_SIGNAL(src, dest, name) \ 32 | (dest)->name.notify = handle_##name; \ 33 | wl_signal_add(&(src)->events.name, &(dest)->name) 34 | 35 | /** 36 | * MIN() - Minimum of two values. 37 | * 38 | * @note Arguments may be evaluated twice. 39 | */ 40 | #ifndef MIN 41 | #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 42 | #endif 43 | 44 | /** 45 | * MAX() - Maximum of two values. 46 | * 47 | * @note Arguments may be evaluated twice. 48 | */ 49 | #ifndef MAX 50 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 51 | #endif 52 | 53 | #endif /* LABWC_MACROS_H */ 54 | -------------------------------------------------------------------------------- /docs/labwc-menu.5.scd: -------------------------------------------------------------------------------- 1 | labwc-menu(5) 2 | 3 | # NAME 4 | 5 | labwc - menu files 6 | 7 | # DESCRIPTION 8 | 9 | Static menus are built based on content of XML files located at 10 | "~/.config/labwc" and equivalent XDG Base Directories. 11 | 12 | # SYNTAX 13 | 14 | The menu file must be entirely enclosed within and 15 | tags. Inside these tags, menus are specified as follows: 16 | 17 | ``` 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 32 | 33 | ...some content... 34 | 35 | 36 | 37 | ``` 38 | 39 | *menu.id* 40 | Each menu must be given an id, which is a unique identifier of the menu. 41 | This id is used to refer to the menu in a ShowMenu action. 42 | Default identifiers are "client-menu" for the titlebar context menu and "root-menu" 43 | for the root window context menu. 44 | Available localisation for the default "client-menu" is 45 | only shown if no "client-menu" is present in menu.xml. 46 | 47 | *menu.label* 48 | The title of the menu, shown in its parent. A label must be given when 49 | defining a menu. 50 | 51 | *menu.item.label* 52 | The visible name of the menu item. 53 | 54 | *menu.item.action* 55 | See labwc-action(5). Note: XML CDATA is supported for this node in 56 | order to maintain compatibility with obmenu-generator. 57 | 58 | *menu.separator* 59 | Horizontal line. 60 | 61 | # SEE ALSO 62 | 63 | labwc(1), labwc-action(5), labwc-config(5), labwc-theme(5) 64 | -------------------------------------------------------------------------------- /docs/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /include/common/scaled_font_buffer.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SCALED_FONT_BUFFER_H 3 | #define LABWC_SCALED_FONT_BUFFER_H 4 | 5 | #include "common/font.h" 6 | 7 | struct wlr_scene_tree; 8 | struct wlr_scene_buffer; 9 | struct scaled_scene_buffer; 10 | 11 | struct scaled_font_buffer { 12 | struct wlr_scene_buffer *scene_buffer; 13 | int width; /* unscaled, read only */ 14 | int height; /* unscaled, read only */ 15 | 16 | /* Private */ 17 | char *text; 18 | int max_width; 19 | float color[4]; 20 | char *arrow; 21 | struct font font; 22 | struct scaled_scene_buffer *scaled_buffer; 23 | }; 24 | 25 | /** 26 | * Create an auto scaling font buffer, providing a wlr_scene_buffer node for 27 | * display. It gets destroyed automatically when the backing scaled_scene_buffer 28 | * is being destroyed which in turn happens automatically when the backing 29 | * wlr_scene_buffer (or one of its parents) is being destroyed. 30 | * 31 | * To actually show some text, scaled_font_buffer_update() has to be called. 32 | * 33 | */ 34 | struct scaled_font_buffer *scaled_font_buffer_create(struct wlr_scene_tree *parent); 35 | 36 | /** 37 | * Update an existing auto scaling font buffer. 38 | * 39 | * No steps are taken to detect if its actually required to render a new buffer. 40 | * This should be done by the caller to prevent useless recreation of the same 41 | * buffer in case nothing actually changed. 42 | * 43 | * Some basic checks could be something like 44 | * - truncated = buffer->width == max_width 45 | * - text_changed = strcmp(old_text, new_text) 46 | * - font and color the same 47 | */ 48 | void scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, 49 | int max_width, struct font *font, float *color, const char *arrow); 50 | 51 | /** 52 | * Update the max width of an existing auto scaling font buffer 53 | * and force a new render. 54 | * 55 | * No steps are taken to detect if its actually required to render a new buffer. 56 | */ 57 | void scaled_font_buffer_set_max_width(struct scaled_font_buffer *self, int max_width); 58 | 59 | #endif /* LABWC_SCALED_FONT_BUFFER_H */ 60 | -------------------------------------------------------------------------------- /src/common/scene-helpers.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "common/scene-helpers.h" 8 | 9 | struct wlr_surface * 10 | lab_wlr_surface_from_node(struct wlr_scene_node *node) 11 | { 12 | struct wlr_scene_buffer *buffer; 13 | struct wlr_scene_surface *scene_surface; 14 | 15 | if (node && node->type == WLR_SCENE_NODE_BUFFER) { 16 | buffer = wlr_scene_buffer_from_node(node); 17 | scene_surface = wlr_scene_surface_try_from_buffer(buffer); 18 | if (scene_surface) { 19 | return scene_surface->surface; 20 | } 21 | } 22 | return NULL; 23 | } 24 | 25 | struct wlr_scene_node * 26 | lab_wlr_scene_get_prev_node(struct wlr_scene_node *node) 27 | { 28 | assert(node); 29 | struct wlr_scene_node *prev; 30 | prev = wl_container_of(node->link.prev, node, link); 31 | if (&prev->link == &node->parent->children) { 32 | return NULL; 33 | } 34 | return prev; 35 | } 36 | 37 | /* 38 | * This is a copy of wlr_scene_output_commit() 39 | * as it doesn't use the pending state at all. 40 | */ 41 | bool 42 | lab_wlr_scene_output_commit(struct wlr_scene_output *scene_output) 43 | { 44 | assert(scene_output); 45 | struct wlr_output *wlr_output = scene_output->output; 46 | struct wlr_output_state *state = &wlr_output->pending; 47 | 48 | if (!wlr_output->needs_frame && !pixman_region32_not_empty( 49 | &scene_output->damage_ring.current)) { 50 | return false; 51 | } 52 | if (!wlr_scene_output_build_state(scene_output, state, NULL)) { 53 | wlr_log(WLR_ERROR, "Failed to build output state for %s", 54 | wlr_output->name); 55 | return false; 56 | } 57 | if (!wlr_output_commit(wlr_output)) { 58 | wlr_log(WLR_ERROR, "Failed to commit output %s", 59 | wlr_output->name); 60 | return false; 61 | } 62 | /* 63 | * FIXME: Remove the following line as soon as 64 | * https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/4253 65 | * is merged. At that point wlr_scene handles damage tracking internally 66 | * again. 67 | */ 68 | wlr_damage_ring_rotate(&scene_output->damage_ring); 69 | return true; 70 | } 71 | -------------------------------------------------------------------------------- /src/button/button-png.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Copyright (C) Johan Malm 2023 4 | */ 5 | #define _POSIX_C_SOURCE 200809L 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "buffer.h" 13 | #include "button/button-png.h" 14 | #include "button/common.h" 15 | #include "common/file-helpers.h" 16 | #include "labwc.h" 17 | #include "theme.h" 18 | 19 | /* 20 | * cairo_image_surface_create_from_png() does not gracefully handle non-png 21 | * files, so we verify the header before trying to read the rest of the file. 22 | */ 23 | #define PNG_BYTES_TO_CHECK (4) 24 | static bool 25 | ispng(const char *filename) 26 | { 27 | unsigned char header[PNG_BYTES_TO_CHECK]; 28 | FILE *fp = fopen(filename, "rb"); 29 | if (!fp) { 30 | return false; 31 | } 32 | if (fread(header, 1, PNG_BYTES_TO_CHECK, fp) != PNG_BYTES_TO_CHECK) { 33 | fclose(fp); 34 | return false; 35 | } 36 | if (png_sig_cmp(header, (png_size_t)0, PNG_BYTES_TO_CHECK)) { 37 | wlr_log(WLR_ERROR, "file '%s' is not a recognised png file", filename); 38 | fclose(fp); 39 | return false; 40 | } 41 | fclose(fp); 42 | return true; 43 | } 44 | 45 | #undef PNG_BYTES_TO_CHECK 46 | 47 | void 48 | button_png_load(const char *button_name, struct lab_data_buffer **buffer) 49 | { 50 | if (*buffer) { 51 | wlr_buffer_drop(&(*buffer)->base); 52 | *buffer = NULL; 53 | } 54 | 55 | char path[4096] = { 0 }; 56 | button_filename(button_name, path, sizeof(path)); 57 | if (!ispng(path)) { 58 | return; 59 | } 60 | 61 | cairo_surface_t *image = cairo_image_surface_create_from_png(path); 62 | if (cairo_surface_status(image)) { 63 | wlr_log(WLR_ERROR, "error reading png button '%s'", path); 64 | cairo_surface_destroy(image); 65 | return; 66 | } 67 | cairo_surface_flush(image); 68 | 69 | double w = cairo_image_surface_get_width(image); 70 | double h = cairo_image_surface_get_height(image); 71 | *buffer = buffer_create_cairo((int)w, (int)h, 1.0, true); 72 | cairo_t *cairo = (*buffer)->cairo; 73 | cairo_set_source_surface(cairo, image, 0, 0); 74 | cairo_paint_with_alpha(cairo, 1.0); 75 | } 76 | -------------------------------------------------------------------------------- /include/buffer.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | /* 3 | * Based on wlroots/include/types/wlr_buffer.c 4 | * 5 | * Copyright (c) 2017, 2018 Drew DeVault 6 | * Copyright (c) 2018-2021 Simon Ser, Simon Zeni 7 | 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | #ifndef LABWC_BUFFER_H 27 | #define LABWC_BUFFER_H 28 | 29 | #include 30 | #include 31 | 32 | struct lab_data_buffer { 33 | struct wlr_buffer base; 34 | 35 | cairo_t *cairo; 36 | void *data; 37 | uint32_t format; 38 | size_t stride; 39 | bool free_on_destroy; 40 | uint32_t unscaled_width; 41 | uint32_t unscaled_height; 42 | }; 43 | 44 | /* Create a buffer which creates a new cairo CAIRO_FORMAT_ARGB32 surface */ 45 | struct lab_data_buffer *buffer_create_cairo(uint32_t width, uint32_t height, 46 | float scale, bool free_on_destroy); 47 | 48 | /* Create a buffer which wraps a given DRM_FORMAT_ARGB8888 pointer */ 49 | struct lab_data_buffer *buffer_create_wrap(void *pixel_data, uint32_t width, 50 | uint32_t height, uint32_t stride, bool free_on_destroy); 51 | 52 | #endif /* LABWC_BUFFER_H */ 53 | -------------------------------------------------------------------------------- /src/button/button-svg.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Copyright (C) Johan Malm 2023 4 | */ 5 | #define _POSIX_C_SOURCE 200809L 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "buffer.h" 13 | #include "button/button-svg.h" 14 | #include "button/common.h" 15 | #include "common/file-helpers.h" 16 | #include "labwc.h" 17 | #include "theme.h" 18 | 19 | void 20 | button_svg_load(const char *button_name, struct lab_data_buffer **buffer, 21 | int size) 22 | { 23 | if (*buffer) { 24 | wlr_buffer_drop(&(*buffer)->base); 25 | *buffer = NULL; 26 | } 27 | 28 | char filename[4096] = { 0 }; 29 | button_filename(button_name, filename, sizeof(filename)); 30 | 31 | GError *err = NULL; 32 | RsvgRectangle viewport = { .width = size, .height = size }; 33 | RsvgHandle *svg = rsvg_handle_new_from_file(filename, &err); 34 | if (err) { 35 | wlr_log(WLR_DEBUG, "error reading svg %s-%s\n", filename, err->message); 36 | g_error_free(err); 37 | /* 38 | * rsvg_handle_new_from_file() returns NULL if an error occurs, 39 | * so there is no need to free svg here. 40 | */ 41 | return; 42 | } 43 | 44 | cairo_surface_t *image = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, size, size); 45 | cairo_t *cr = cairo_create(image); 46 | 47 | rsvg_handle_render_document(svg, cr, &viewport, &err); 48 | if (err) { 49 | wlr_log(WLR_ERROR, "error rendering svg %s-%s\n", filename, err->message); 50 | g_error_free(err); 51 | goto error; 52 | } 53 | 54 | if (cairo_surface_status(image)) { 55 | wlr_log(WLR_ERROR, "error reading svg button '%s'", filename); 56 | goto error; 57 | } 58 | cairo_surface_flush(image); 59 | 60 | double w = cairo_image_surface_get_width(image); 61 | double h = cairo_image_surface_get_height(image); 62 | *buffer = buffer_create_cairo((int)w, (int)h, 1.0, /* free_on_destroy */ true); 63 | cairo_t *cairo = (*buffer)->cairo; 64 | cairo_set_source_surface(cairo, image, 0, 0); 65 | cairo_paint_with_alpha(cairo, 1.0); 66 | 67 | error: 68 | cairo_destroy(cr); 69 | cairo_surface_destroy(image); 70 | g_object_unref(svg); 71 | } 72 | -------------------------------------------------------------------------------- /include/xwayland.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_XWAYLAND_H 3 | #define LABWC_XWAYLAND_H 4 | #include "config.h" 5 | #if HAVE_XWAYLAND 6 | #include "view.h" 7 | 8 | struct wlr_compositor; 9 | struct wlr_output; 10 | struct wlr_output_layout; 11 | 12 | struct xwayland_unmanaged { 13 | struct server *server; 14 | struct wlr_xwayland_surface *xwayland_surface; 15 | struct wlr_scene_node *node; 16 | struct wl_list link; 17 | 18 | struct mappable mappable; 19 | 20 | struct wl_listener associate; 21 | struct wl_listener dissociate; 22 | struct wl_listener request_activate; 23 | struct wl_listener request_configure; 24 | /* struct wl_listener request_fullscreen; */ 25 | struct wl_listener set_geometry; 26 | struct wl_listener destroy; 27 | struct wl_listener set_override_redirect; 28 | }; 29 | 30 | struct xwayland_view { 31 | struct view base; 32 | struct wlr_xwayland_surface *xwayland_surface; 33 | 34 | /* Events unique to XWayland views */ 35 | struct wl_listener associate; 36 | struct wl_listener dissociate; 37 | struct wl_listener request_activate; 38 | struct wl_listener request_configure; 39 | struct wl_listener set_class; 40 | struct wl_listener set_decorations; 41 | struct wl_listener set_override_redirect; 42 | struct wl_listener set_strut_partial; 43 | 44 | /* Not (yet) implemented */ 45 | /* struct wl_listener set_role; */ 46 | /* struct wl_listener set_window_type; */ 47 | /* struct wl_listener set_hints; */ 48 | }; 49 | 50 | void xwayland_unmanaged_create(struct server *server, 51 | struct wlr_xwayland_surface *xsurface, bool mapped); 52 | 53 | void xwayland_view_create(struct server *server, 54 | struct wlr_xwayland_surface *xsurface, bool mapped); 55 | 56 | void xwayland_adjust_stacking_order(struct server *server); 57 | 58 | struct wlr_xwayland_surface *xwayland_surface_from_view(struct view *view); 59 | 60 | void xwayland_server_init(struct server *server, 61 | struct wlr_compositor *compositor); 62 | void xwayland_server_finish(struct server *server); 63 | 64 | void xwayland_adjust_usable_area(struct view *view, 65 | struct wlr_output_layout *layout, struct wlr_output *output, 66 | struct wlr_box *usable); 67 | 68 | void xwayland_update_workarea(struct server *server); 69 | 70 | #endif /* HAVE_XWAYLAND */ 71 | #endif /* LABWC_XWAYLAND_H */ 72 | -------------------------------------------------------------------------------- /docs/themerc: -------------------------------------------------------------------------------- 1 | # This file contains all themerc options with default values 2 | # 3 | # System-wide and local themes can be overridden by creating a copy of this 4 | # file and renaming it to $HOME/.config/labwc/themerc-override. Be careful 5 | # though - if you only want to override a small number of specific options, 6 | # make sure all other lines are commented out or deleted. 7 | 8 | # general 9 | border.width: 1 10 | padding.height: 3 11 | 12 | # The following options has no default, but fallbacks back to 13 | # font-height + 2x padding.height if not set. 14 | # titlebar.height: 15 | 16 | # window border 17 | window.active.border.color: #e1dedb 18 | window.inactive.border.color: #f6f5f4 19 | 20 | # ToggleKeybinds status indicator 21 | window.active.indicator.toggled-keybind.color: #ff0000 22 | 23 | # window titlebar background 24 | window.active.title.bg.color: #e1dedb 25 | window.inactive.title.bg.color: #f6f5f4 26 | 27 | # window titlebar text 28 | window.active.label.text.color: #000000 29 | window.inactive.label.text.color: #000000 30 | window.label.text.justify: center 31 | 32 | # window buttons 33 | window.active.button.unpressed.image.color: #000000 34 | window.inactive.button.unpressed.image.color: #000000 35 | 36 | # Note that "menu", "iconify", "max", "close" buttons colors can be defined 37 | # individually by inserting the type after the button node, for example: 38 | # 39 | # window.active.button.iconify.unpressed.image.color: #333333 40 | 41 | # menu 42 | menu.overlap.x: 0 43 | menu.overlap.y: 0 44 | menu.width.min: 20 45 | menu.width.max: 200 46 | menu.items.bg.color: #fcfbfa 47 | menu.items.text.color: #000000 48 | menu.items.active.bg.color: #e1dedb 49 | menu.items.active.text.color: #000000 50 | menu.items.padding.x: 7 51 | menu.items.padding.y: 4 52 | menu.separator.width: 1 53 | menu.separator.padding.width: 6 54 | menu.separator.padding.height: 3 55 | menu.separator.color: #888888 56 | 57 | # on screen display (window-cycle dialog) 58 | osd.bg.color: #dddda6 59 | osd.border.color: #000000 60 | osd.border.width: 1 61 | osd.label.text.color: #000000 62 | 63 | osd.window-switcher.width: 600 64 | osd.window-switcher.padding: 4 65 | osd.window-switcher.item.padding.x: 10 66 | osd.window-switcher.item.padding.y: 1 67 | osd.window-switcher.item.active.border.width: 2 68 | 69 | osd.workspace-switcher.boxes.width: 20 70 | osd.workspace-switcher.boxes.height: 20 71 | -------------------------------------------------------------------------------- /docs/environment: -------------------------------------------------------------------------------- 1 | ## 2 | ## Example ~/.config/labwc/environment file. 3 | ## Uncomment lines starting with one '#' to suit your needs. 4 | ## 5 | 6 | ## 7 | ## Use the XKB_DEFAULT_LAYOUT variable to set the keyboard layout. For example 8 | ## to start with Swedish keyboard layout set it to 'se'. If you are unsure what 9 | ## your country code is, refer to the layout section of: 10 | ## /usr/share/X11/xkb/rules/evdev.lst 11 | ## 12 | ## Multiple keyboard layouts can be set by comma-separating the country codes. 13 | ## If a variant layout is needed, the syntax is layout(variant) 14 | ## If multiple layouts are used, specify the toggle-keybind using 15 | ## XKB_DEFAULT_OPTIONS as show below. 16 | ## 17 | ## For further details, see xkeyboard-config(7) 18 | ## 19 | 20 | # XKB_DEFAULT_LAYOUT=se 21 | # XKB_DEFAULT_LAYOUT=se,us(intl) 22 | # XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle 23 | # XKB_DEFAULT_OPTIONS=grp:shift_caps_toggle 24 | 25 | ## 26 | ## Force firefox to use wayland backend. 27 | ## 28 | 29 | # MOZ_ENABLE_WAYLAND=1 30 | 31 | ## 32 | ## Set cursor theme and size. Find system icons themes with: 33 | ## `find /usr/share/icons/ -type d -name "cursors"` 34 | ## 35 | 36 | # XCURSOR_THEME=breeze_cursors 37 | # XCURSOR_THEME=capitaine-cursors 38 | # XCURSOR_SIZE=24 39 | 40 | ## 41 | ## Disable hardware cursors. Most users would not want to do this, but if you 42 | ## are experiencing issues with disappearing cursors, this might fix it. 43 | ## 44 | 45 | # WLR_NO_HARDWARE_CURSORS=1 46 | 47 | ## 48 | ## In order for labwc to work out of the box, the environment variable below 49 | ## is set to "1" by default to avoid menus with incorrect offset and blank 50 | ## windows with Java applications such as JetBrains and Intellij Idea. 51 | ## See https://github.com/swaywm/sway/issues/595 52 | ## labwc will not override any already set environment variables, so if you for 53 | ## some reason do not want this, then just set it to "0" (not recommended, but 54 | ## mentioned here for completeness). 55 | ## 56 | 57 | # _JAVA_AWT_WM_NONREPARENTING=0 58 | 59 | ## 60 | ## This allows xdg-desktop-portal-wlr to function (e.g. for screen-recording). 61 | ## It is automatically set to "wlroots" by labwc though, so it is only 62 | ## includeded here for completeness. Again, labwc will not over-write an 63 | ## already set environment variable, so if you need it set to something else, 64 | ## then uncomment and adjust. 65 | ## 66 | 67 | # XDG_CURRENT_DESKTOP=wlroots 68 | 69 | -------------------------------------------------------------------------------- /src/common/buf.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include "common/buf.h" 5 | #include "common/mem.h" 6 | 7 | static void 8 | buf_add_one_char(struct buf *s, char ch) 9 | { 10 | if (s->alloc <= s->len + 1) { 11 | s->alloc = s->alloc * 3 / 2 + 16; 12 | s->buf = xrealloc(s->buf, s->alloc); 13 | } 14 | s->buf[s->len++] = ch; 15 | s->buf[s->len] = '\0'; 16 | } 17 | 18 | void 19 | buf_expand_tilde(struct buf *s) 20 | { 21 | struct buf new; 22 | buf_init(&new); 23 | for (int i = 0 ; i < s->len ; i++) { 24 | if (s->buf[i] == '~') { 25 | buf_add(&new, getenv("HOME")); 26 | } else { 27 | buf_add_one_char(&new, s->buf[i]); 28 | } 29 | } 30 | free(s->buf); 31 | s->buf = new.buf; 32 | s->len = new.len; 33 | s->alloc = new.alloc; 34 | } 35 | 36 | static void 37 | strip_curly_braces(char *s) 38 | { 39 | size_t len = strlen(s); 40 | if (s[0] != '{' || s[len - 1] != '}') { 41 | return; 42 | } 43 | len -= 2; 44 | memmove(s, s + 1, len); 45 | s[len] = 0; 46 | } 47 | 48 | static bool 49 | isvalid(char p) 50 | { 51 | return isalnum(p) || p == '_' || p == '{' || p == '}'; 52 | } 53 | 54 | void 55 | buf_expand_shell_variables(struct buf *s) 56 | { 57 | struct buf new; 58 | struct buf environment_variable; 59 | buf_init(&new); 60 | buf_init(&environment_variable); 61 | 62 | for (int i = 0 ; i < s->len ; i++) { 63 | if (s->buf[i] == '$' && isvalid(s->buf[i+1])) { 64 | /* expand environment variable */ 65 | environment_variable.len = 0; 66 | buf_add(&environment_variable, s->buf + i + 1); 67 | char *p = environment_variable.buf; 68 | while (isvalid(*p)) { 69 | ++p; 70 | } 71 | *p = '\0'; 72 | i += strlen(environment_variable.buf); 73 | strip_curly_braces(environment_variable.buf); 74 | p = getenv(environment_variable.buf); 75 | if (p) { 76 | buf_add(&new, p); 77 | } 78 | } else { 79 | buf_add_one_char(&new, s->buf[i]); 80 | } 81 | } 82 | free(environment_variable.buf); 83 | free(s->buf); 84 | s->buf = new.buf; 85 | s->len = new.len; 86 | s->alloc = new.alloc; 87 | } 88 | 89 | void 90 | buf_init(struct buf *s) 91 | { 92 | s->alloc = 256; 93 | s->buf = xmalloc(s->alloc); 94 | s->buf[0] = '\0'; 95 | s->len = 0; 96 | } 97 | 98 | void 99 | buf_add(struct buf *s, const char *data) 100 | { 101 | if (!data || data[0] == '\0') { 102 | return; 103 | } 104 | int len = strlen(data); 105 | if (s->alloc <= s->len + len + 1) { 106 | s->alloc = s->alloc + len; 107 | s->buf = xrealloc(s->buf, s->alloc); 108 | } 109 | memcpy(s->buf + s->len, data, len); 110 | s->len += len; 111 | s->buf[s->len] = 0; 112 | } 113 | -------------------------------------------------------------------------------- /src/node.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include "common/mem.h" 5 | #include "node.h" 6 | 7 | static void 8 | descriptor_destroy(struct node_descriptor *node_descriptor) 9 | { 10 | if (!node_descriptor) { 11 | return; 12 | } 13 | wl_list_remove(&node_descriptor->destroy.link); 14 | free(node_descriptor); 15 | } 16 | 17 | static void 18 | destroy_notify(struct wl_listener *listener, void *data) 19 | { 20 | struct node_descriptor *node_descriptor = 21 | wl_container_of(listener, node_descriptor, destroy); 22 | descriptor_destroy(node_descriptor); 23 | } 24 | 25 | void 26 | node_descriptor_create(struct wlr_scene_node *scene_node, 27 | enum node_descriptor_type type, void *data) 28 | { 29 | struct node_descriptor *node_descriptor = znew(*node_descriptor); 30 | node_descriptor->type = type; 31 | node_descriptor->data = data; 32 | node_descriptor->destroy.notify = destroy_notify; 33 | wl_signal_add(&scene_node->events.destroy, &node_descriptor->destroy); 34 | scene_node->data = node_descriptor; 35 | } 36 | 37 | struct view * 38 | node_view_from_node(struct wlr_scene_node *wlr_scene_node) 39 | { 40 | assert(wlr_scene_node->data); 41 | struct node_descriptor *node_descriptor = wlr_scene_node->data; 42 | assert(node_descriptor->type == LAB_NODE_DESC_VIEW 43 | || node_descriptor->type == LAB_NODE_DESC_XDG_POPUP); 44 | return (struct view *)node_descriptor->data; 45 | } 46 | 47 | struct lab_layer_surface * 48 | node_layer_surface_from_node(struct wlr_scene_node *wlr_scene_node) 49 | { 50 | assert(wlr_scene_node->data); 51 | struct node_descriptor *node_descriptor = wlr_scene_node->data; 52 | assert(node_descriptor->type == LAB_NODE_DESC_LAYER_SURFACE); 53 | return (struct lab_layer_surface *)node_descriptor->data; 54 | } 55 | 56 | struct lab_layer_popup * 57 | node_layer_popup_from_node(struct wlr_scene_node *wlr_scene_node) 58 | { 59 | assert(wlr_scene_node->data); 60 | struct node_descriptor *node_descriptor = wlr_scene_node->data; 61 | assert(node_descriptor->type == LAB_NODE_DESC_LAYER_POPUP); 62 | return (struct lab_layer_popup *)node_descriptor->data; 63 | } 64 | 65 | struct menuitem * 66 | node_menuitem_from_node(struct wlr_scene_node *wlr_scene_node) 67 | { 68 | assert(wlr_scene_node->data); 69 | struct node_descriptor *node_descriptor = wlr_scene_node->data; 70 | assert(node_descriptor->type == LAB_NODE_DESC_MENUITEM); 71 | return (struct menuitem *)node_descriptor->data; 72 | } 73 | 74 | struct ssd_button * 75 | node_ssd_button_from_node(struct wlr_scene_node *wlr_scene_node) 76 | { 77 | assert(wlr_scene_node->data); 78 | struct node_descriptor *node_descriptor = wlr_scene_node->data; 79 | assert(node_descriptor->type == LAB_NODE_DESC_SSD_BUTTON); 80 | return (struct ssd_button *)node_descriptor->data; 81 | } 82 | -------------------------------------------------------------------------------- /include/node.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_NODE_DESCRIPTOR_H 3 | #define LABWC_NODE_DESCRIPTOR_H 4 | #include 5 | 6 | struct view; 7 | struct lab_layer_surface; 8 | struct lab_layer_popup; 9 | struct menuitem; 10 | struct ssd_button; 11 | 12 | enum node_descriptor_type { 13 | LAB_NODE_DESC_NODE = 0, 14 | LAB_NODE_DESC_VIEW, 15 | LAB_NODE_DESC_XDG_POPUP, 16 | LAB_NODE_DESC_LAYER_SURFACE, 17 | LAB_NODE_DESC_LAYER_POPUP, 18 | LAB_NODE_DESC_MENUITEM, 19 | LAB_NODE_DESC_TREE, 20 | LAB_NODE_DESC_SSD_BUTTON, 21 | }; 22 | 23 | struct node_descriptor { 24 | enum node_descriptor_type type; 25 | void *data; 26 | struct wl_listener destroy; 27 | }; 28 | 29 | /** 30 | * node_descriptor_create - create node descriptor for wlr_scene_node user_data 31 | * 32 | * The node_descriptor will be destroyed automatically 33 | * once the scene_node it is attached to is destroyed. 34 | * 35 | * @scene_node: wlr_scene_node to attached node_descriptor to 36 | * @type: node descriptor type 37 | * @data: struct to point to as follows: 38 | * - LAB_NODE_DESC_VIEW struct view 39 | * - LAB_NODE_DESC_XDG_POPUP struct view 40 | * - LAB_NODE_DESC_LAYER_SURFACE struct lab_layer_surface 41 | * - LAB_NODE_DESC_LAYER_POPUP struct lab_layer_popup 42 | * - LAB_NODE_DESC_MENUITEM struct menuitem 43 | * - LAB_NODE_DESC_SSD_BUTTON struct ssd_button 44 | */ 45 | void node_descriptor_create(struct wlr_scene_node *scene_node, 46 | enum node_descriptor_type type, void *data); 47 | 48 | /** 49 | * node_view_from_node - return view struct from node 50 | * @wlr_scene_node: wlr_scene_node from which to return data 51 | */ 52 | struct view *node_view_from_node(struct wlr_scene_node *wlr_scene_node); 53 | 54 | /** 55 | * node_lab_surface_from_node - return lab_layer_surface struct from node 56 | * @wlr_scene_node: wlr_scene_node from which to return data 57 | */ 58 | struct lab_layer_surface *node_layer_surface_from_node( 59 | struct wlr_scene_node *wlr_scene_node); 60 | 61 | /** 62 | * node_layer_popup_from_node - return lab_layer_popup struct from node 63 | * @wlr_scene_node: wlr_scene_node from which to return data 64 | */ 65 | struct lab_layer_popup *node_layer_popup_from_node( 66 | struct wlr_scene_node *wlr_scene_node); 67 | 68 | /** 69 | * node_menuitem_from_node - return menuitem struct from node 70 | * @wlr_scene_node: wlr_scene_node from which to return data 71 | */ 72 | struct menuitem *node_menuitem_from_node( 73 | struct wlr_scene_node *wlr_scene_node); 74 | 75 | /** 76 | * node_ssd_button_from_node - return ssd_button struct from node 77 | * @wlr_scene_node: wlr_scene_node from which to return data 78 | */ 79 | struct ssd_button *node_ssd_button_from_node( 80 | struct wlr_scene_node *wlr_scene_node); 81 | 82 | #endif /* LABWC_NODE_DESCRIPTOR_H */ 83 | -------------------------------------------------------------------------------- /include/regions.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_REGIONS_H 3 | #define LABWC_REGIONS_H 4 | 5 | #include 6 | 7 | struct seat; 8 | struct view; 9 | struct server; 10 | struct output; 11 | struct wl_list; 12 | struct wlr_box; 13 | struct multi_rect; 14 | 15 | /* Double use: rcxml.c for config and output.c for usage */ 16 | struct region { 17 | struct wl_list link; /* struct rcxml.regions, struct output.regions */ 18 | struct output *output; 19 | char *name; 20 | struct wlr_box geo; 21 | struct wlr_box percentage; 22 | struct { 23 | int x; 24 | int y; 25 | } center; 26 | }; 27 | 28 | struct region_overlay { 29 | struct wlr_scene_tree *tree; 30 | union { 31 | struct wlr_scene_rect *overlay; 32 | struct multi_rect *pixman_overlay; 33 | }; 34 | }; 35 | 36 | /* Returns true if we should show the region overlay or snap to region */ 37 | bool regions_should_snap(struct server *server); 38 | 39 | /** 40 | * regions_reconfigure*() - re-initializes all regions from struct rc. 41 | * 42 | * - all views are evacuated from the given output (or all of them) 43 | * - all output local regions are destroyed 44 | * - new output local regions are created from struct rc 45 | * - the region geometry is re-calculated 46 | */ 47 | void regions_reconfigure(struct server *server); 48 | void regions_reconfigure_output(struct output *output); 49 | 50 | /* re-calculate the geometry based on usable area */ 51 | void regions_update_geometry(struct output *output); 52 | 53 | /** 54 | * Mark all views which are currently region-tiled to the given output as 55 | * evacuated. This means that the view->tiled_region pointer is reset to 56 | * NULL but view->tiled_region_evacuate is set to a copy of the region name. 57 | * 58 | * The next time desktop_arrange_all_views() causes a call to 59 | * view_apply_region_geometry() it will try to find a new output and then 60 | * search for a region with the same name. If found, view->tiled_region will 61 | * be set to the new region and view->tiled_region_evacuate will be free'd. 62 | * 63 | * If no region with the old name is found (e.g. the user deleted or renamed 64 | * the region in rc.xml and caused a Reconfigure) the view will be reset to 65 | * non-tiled state and view->tiled_region_evacuate will be free'd. 66 | */ 67 | void regions_evacuate_output(struct output *output); 68 | 69 | /* Free all regions in given wl_list pointer */ 70 | void regions_destroy(struct seat *seat, struct wl_list *regions); 71 | 72 | /* Get output local region from cursor or name, may be NULL */ 73 | struct region *regions_from_cursor(struct server *server); 74 | struct region *regions_from_name(const char *region_name, struct output *output); 75 | 76 | void regions_show_overlay(struct view *view, struct seat *seat, struct region *region); 77 | void regions_hide_overlay(struct seat *seat); 78 | 79 | #endif /* LABWC_REGIONS_H */ 80 | -------------------------------------------------------------------------------- /include/config/rcxml.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_RCXML_H 3 | #define LABWC_RCXML_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "common/border.h" 10 | #include "common/buf.h" 11 | #include "common/font.h" 12 | #include "config/libinput.h" 13 | #include "resize_indicator.h" 14 | #include "theme.h" 15 | 16 | enum window_switcher_field_content { 17 | LAB_FIELD_NONE = 0, 18 | LAB_FIELD_TYPE, 19 | LAB_FIELD_IDENTIFIER, 20 | LAB_FIELD_TITLE, 21 | }; 22 | 23 | struct usable_area_override { 24 | struct border margin; 25 | char *output; 26 | struct wl_list link; /* struct rcxml.usable_area_overrides */ 27 | }; 28 | 29 | struct window_switcher_field { 30 | enum window_switcher_field_content content; 31 | int width; 32 | struct wl_list link; /* struct rcxml.window_switcher.fields */ 33 | }; 34 | 35 | struct rcxml { 36 | char *config_dir; 37 | 38 | /* core */ 39 | bool xdg_shell_server_side_deco; 40 | int gap; 41 | bool adaptive_sync; 42 | bool reuse_output_mode; 43 | 44 | /* focus */ 45 | bool focus_follow_mouse; 46 | bool focus_follow_mouse_requires_movement; 47 | bool raise_on_focus; 48 | 49 | /* theme */ 50 | char *theme_name; 51 | int corner_radius; 52 | bool ssd_keep_border; 53 | struct font font_activewindow; 54 | struct font font_inactivewindow; 55 | struct font font_menuitem; 56 | struct font font_osd; 57 | /* Pointer to current theme */ 58 | struct theme *theme; 59 | 60 | /* */ 61 | struct wl_list usable_area_overrides; 62 | 63 | /* keyboard */ 64 | int repeat_rate; 65 | int repeat_delay; 66 | bool kb_numlock_enable; 67 | bool kb_layout_per_window; 68 | struct wl_list keybinds; /* struct keybind.link */ 69 | 70 | /* mouse */ 71 | long doubleclick_time; /* in ms */ 72 | struct wl_list mousebinds; /* struct mousebind.link */ 73 | double scroll_factor; 74 | 75 | /* libinput */ 76 | struct wl_list libinput_categories; 77 | 78 | /* resistance */ 79 | int screen_edge_strength; 80 | 81 | /* window snapping */ 82 | int snap_edge_range; 83 | bool snap_top_maximize; 84 | 85 | enum resize_indicator_mode resize_indicator; 86 | 87 | struct { 88 | int popuptime; 89 | int min_nr_workspaces; 90 | struct wl_list workspaces; /* struct workspace.link */ 91 | } workspace_config; 92 | 93 | /* Regions */ 94 | struct wl_list regions; /* struct region.link */ 95 | 96 | struct { 97 | bool show; 98 | bool preview; 99 | bool outlines; 100 | struct wl_list fields; /* struct window_switcher_field.link */ 101 | } window_switcher; 102 | 103 | struct wl_list window_rules; /* struct window_rule.link */ 104 | }; 105 | 106 | extern struct rcxml rc; 107 | 108 | void rcxml_parse_xml(struct buf *b); 109 | void rcxml_read(const char *filename); 110 | void rcxml_finish(void); 111 | 112 | #endif /* LABWC_RCXML_H */ 113 | -------------------------------------------------------------------------------- /src/common/graphic-helpers.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "common/graphic-helpers.h" 9 | #include "common/mem.h" 10 | 11 | static void 12 | multi_rect_destroy_notify(struct wl_listener *listener, void *data) 13 | { 14 | struct multi_rect *rect = wl_container_of(listener, rect, destroy); 15 | free(rect); 16 | } 17 | 18 | struct multi_rect * 19 | multi_rect_create(struct wlr_scene_tree *parent, float *colors[3], int line_width) 20 | { 21 | struct multi_rect *rect = znew(*rect); 22 | rect->line_width = line_width; 23 | rect->tree = wlr_scene_tree_create(parent); 24 | rect->destroy.notify = multi_rect_destroy_notify; 25 | wl_signal_add(&rect->tree->node.events.destroy, &rect->destroy); 26 | for (size_t i = 0; i < 3; i++) { 27 | rect->top[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); 28 | rect->right[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); 29 | rect->bottom[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); 30 | rect->left[i] = wlr_scene_rect_create(rect->tree, 0, 0, colors[i]); 31 | wlr_scene_node_set_position(&rect->top[i]->node, 32 | i * line_width, i * line_width); 33 | wlr_scene_node_set_position(&rect->left[i]->node, 34 | i * line_width, i * line_width); 35 | } 36 | return rect; 37 | } 38 | 39 | void 40 | multi_rect_set_size(struct multi_rect *rect, int width, int height) 41 | { 42 | assert(rect); 43 | int line_width = rect->line_width; 44 | 45 | for (size_t i = 0; i < 3; i++) { 46 | /* Reposition, top and left don't ever change */ 47 | wlr_scene_node_set_position(&rect->right[i]->node, 48 | width - (i + 1) * line_width, i * line_width); 49 | wlr_scene_node_set_position(&rect->bottom[i]->node, 50 | i * line_width, height - (i + 1) * line_width); 51 | 52 | /* Update sizes */ 53 | wlr_scene_rect_set_size(rect->top[i], 54 | width - i * line_width * 2, line_width); 55 | wlr_scene_rect_set_size(rect->bottom[i], 56 | width - i * line_width * 2, line_width); 57 | wlr_scene_rect_set_size(rect->left[i], 58 | line_width, height - i * line_width * 2); 59 | wlr_scene_rect_set_size(rect->right[i], 60 | line_width, height - i * line_width * 2); 61 | } 62 | } 63 | 64 | /* Draws a border with a specified line width */ 65 | void 66 | draw_cairo_border(cairo_t *cairo, struct wlr_fbox fbox, double line_width) 67 | { 68 | cairo_save(cairo); 69 | 70 | /* The anchor point of a line is in the center */ 71 | fbox.x += line_width / 2.0; 72 | fbox.y += line_width / 2.0; 73 | fbox.width -= line_width; 74 | fbox.height -= line_width; 75 | cairo_set_line_width(cairo, line_width); 76 | cairo_rectangle(cairo, fbox.x, fbox.y, fbox.width, fbox.height); 77 | cairo_stroke(cairo); 78 | 79 | cairo_restore(cairo); 80 | } 81 | 82 | /* Sets the cairo color. Splits the single color channels */ 83 | void 84 | set_cairo_color(cairo_t *cairo, float *c) 85 | { 86 | cairo_set_source_rgba(cairo, c[0], c[1], c[2], c[3]); 87 | } 88 | -------------------------------------------------------------------------------- /src/common/scaled_font_buffer.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #define _POSIX_C_SOURCE 200809L 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "buffer.h" 9 | #include "common/font.h" 10 | #include "common/mem.h" 11 | #include "common/scaled_scene_buffer.h" 12 | #include "common/scaled_font_buffer.h" 13 | 14 | static struct lab_data_buffer * 15 | _create_buffer(struct scaled_scene_buffer *scaled_buffer, double scale) 16 | { 17 | struct lab_data_buffer *buffer; 18 | struct scaled_font_buffer *self = scaled_buffer->data; 19 | 20 | /* Buffer gets free'd automatically along the backing wlr_buffer */ 21 | font_buffer_create(&buffer, self->max_width, self->text, 22 | &self->font, self->color, self->arrow, scale); 23 | 24 | self->width = buffer ? buffer->unscaled_width : 0; 25 | self->height = buffer ? buffer->unscaled_height : 0; 26 | return buffer; 27 | } 28 | 29 | static void 30 | _destroy(struct scaled_scene_buffer *scaled_buffer) 31 | { 32 | struct scaled_font_buffer *self = scaled_buffer->data; 33 | scaled_buffer->data = NULL; 34 | 35 | zfree(self->text); 36 | zfree(self->font.name); 37 | zfree(self->arrow); 38 | free(self); 39 | } 40 | 41 | static const struct scaled_scene_buffer_impl impl = { 42 | .create_buffer = _create_buffer, 43 | .destroy = _destroy 44 | }; 45 | 46 | /* Public API */ 47 | struct scaled_font_buffer * 48 | scaled_font_buffer_create(struct wlr_scene_tree *parent) 49 | { 50 | assert(parent); 51 | struct scaled_font_buffer *self = znew(*self); 52 | struct scaled_scene_buffer *scaled_buffer = 53 | scaled_scene_buffer_create(parent, &impl, /* drop_buffer */ true); 54 | if (!scaled_buffer) { 55 | free(self); 56 | return NULL; 57 | } 58 | 59 | scaled_buffer->data = self; 60 | self->scaled_buffer = scaled_buffer; 61 | self->scene_buffer = scaled_buffer->scene_buffer; 62 | return self; 63 | } 64 | 65 | void 66 | scaled_font_buffer_update(struct scaled_font_buffer *self, const char *text, 67 | int max_width, struct font *font, float *color, 68 | const char *arrow) 69 | { 70 | assert(self); 71 | assert(text); 72 | assert(font); 73 | assert(color); 74 | 75 | /* Clean up old internal state */ 76 | zfree(self->text); 77 | zfree(self->font.name); 78 | zfree(self->arrow); 79 | 80 | /* Update internal state */ 81 | self->text = xstrdup(text); 82 | self->max_width = max_width; 83 | if (font->name) { 84 | self->font.name = xstrdup(font->name); 85 | } 86 | self->font.size = font->size; 87 | self->font.slant = font->slant; 88 | self->font.weight = font->weight; 89 | memcpy(self->color, color, sizeof(self->color)); 90 | self->arrow = arrow ? xstrdup(arrow) : NULL; 91 | 92 | /* Invalidate cache and force a new render */ 93 | scaled_scene_buffer_invalidate_cache(self->scaled_buffer); 94 | } 95 | 96 | void 97 | scaled_font_buffer_set_max_width(struct scaled_font_buffer *self, int max_width) 98 | { 99 | self->max_width = max_width; 100 | scaled_scene_buffer_invalidate_cache(self->scaled_buffer); 101 | } 102 | -------------------------------------------------------------------------------- /include/ssd.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SSD_H 3 | #define LABWC_SSD_H 4 | 5 | #include 6 | #include "common/border.h" 7 | 8 | #define SSD_BUTTON_COUNT 4 9 | #define SSD_BUTTON_WIDTH 26 10 | #define SSD_EXTENDED_AREA 8 11 | 12 | /* 13 | * Sequence these according to the order they should be processed for 14 | * press and hover events. Bear in mind that some of their respective 15 | * interactive areas overlap, so for example buttons need to come before title. 16 | */ 17 | enum ssd_part_type { 18 | LAB_SSD_NONE = 0, 19 | LAB_SSD_BUTTON_CLOSE, 20 | LAB_SSD_BUTTON_MAXIMIZE, 21 | LAB_SSD_BUTTON_ICONIFY, 22 | LAB_SSD_BUTTON_WINDOW_MENU, 23 | LAB_SSD_PART_TITLEBAR, 24 | LAB_SSD_PART_TITLE, 25 | LAB_SSD_PART_CORNER_TOP_LEFT, 26 | LAB_SSD_PART_CORNER_TOP_RIGHT, 27 | LAB_SSD_PART_CORNER_BOTTOM_RIGHT, 28 | LAB_SSD_PART_CORNER_BOTTOM_LEFT, 29 | LAB_SSD_PART_TOP, 30 | LAB_SSD_PART_RIGHT, 31 | LAB_SSD_PART_BOTTOM, 32 | LAB_SSD_PART_LEFT, 33 | LAB_SSD_CLIENT, 34 | LAB_SSD_FRAME, 35 | LAB_SSD_ROOT, 36 | LAB_SSD_MENU, 37 | LAB_SSD_OSD, 38 | LAB_SSD_LAYER_SURFACE, 39 | LAB_SSD_LAYER_SUBSURFACE, 40 | LAB_SSD_UNMANAGED, 41 | LAB_SSD_END_MARKER 42 | }; 43 | 44 | /* Forward declare arguments */ 45 | struct ssd; 46 | struct ssd_button; 47 | struct ssd_hover_state; 48 | struct view; 49 | struct wlr_scene; 50 | struct wlr_scene_node; 51 | 52 | /* 53 | * Public SSD API 54 | * 55 | * For convenience in dealing with non-SSD views, this API allows NULL 56 | * ssd/button/node arguments and attempts to do something sensible in 57 | * that case (e.g. no-op/return default values). 58 | * 59 | * NULL scene/view arguments are not allowed. 60 | */ 61 | struct ssd *ssd_create(struct view *view, bool active); 62 | struct border ssd_get_margin(const struct ssd *ssd); 63 | void ssd_update_margin(struct ssd *ssd); 64 | void ssd_set_active(struct ssd *ssd, bool active); 65 | void ssd_update_title(struct ssd *ssd); 66 | void ssd_update_geometry(struct ssd *ssd); 67 | void ssd_destroy(struct ssd *ssd); 68 | void ssd_titlebar_hide(struct ssd *ssd); 69 | 70 | void ssd_enable_keybind_inhibit_indicator(struct ssd *ssd, bool enable); 71 | 72 | struct ssd_hover_state *ssd_hover_state_new(void); 73 | void ssd_update_button_hover(struct wlr_scene_node *node, 74 | struct ssd_hover_state *hover_state); 75 | 76 | enum ssd_part_type ssd_button_get_type(const struct ssd_button *button); 77 | struct view *ssd_button_get_view(const struct ssd_button *button); 78 | 79 | /* Public SSD helpers */ 80 | enum ssd_part_type ssd_at(const struct ssd *ssd, 81 | struct wlr_scene *scene, double lx, double ly); 82 | enum ssd_part_type ssd_get_part_type(const struct ssd *ssd, 83 | struct wlr_scene_node *node); 84 | uint32_t ssd_resize_edges(enum ssd_part_type type); 85 | bool ssd_is_button(enum ssd_part_type type); 86 | bool ssd_part_contains(enum ssd_part_type whole, enum ssd_part_type candidate); 87 | 88 | /* TODO: clean up / update */ 89 | struct border ssd_thickness(struct view *view); 90 | struct wlr_box ssd_max_extents(struct view *view); 91 | 92 | /* SSD debug helpers */ 93 | bool ssd_debug_is_root_node(const struct ssd *ssd, struct wlr_scene_node *node); 94 | const char *ssd_debug_get_node_name(const struct ssd *ssd, 95 | struct wlr_scene_node *node); 96 | 97 | #endif /* LABWC_SSD_H */ 98 | -------------------------------------------------------------------------------- /src/idle.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "common/mem.h" 8 | #include "idle.h" 9 | 10 | struct lab_idle_inhibitor { 11 | struct wlr_idle_inhibitor_v1 *wlr_inhibitor; 12 | struct wl_listener on_destroy; 13 | }; 14 | 15 | struct lab_idle_manager { 16 | struct wlr_idle_notifier_v1 *ext; 17 | struct { 18 | struct wlr_idle_inhibit_manager_v1 *manager; 19 | struct wl_listener on_new_inhibitor; 20 | } inhibitor; 21 | struct wlr_seat *wlr_seat; 22 | struct wl_listener on_display_destroy; 23 | }; 24 | 25 | static struct lab_idle_manager *manager; 26 | 27 | static void 28 | handle_idle_inhibitor_destroy(struct wl_listener *listener, void *data) 29 | { 30 | struct lab_idle_inhibitor *idle_inhibitor = wl_container_of(listener, 31 | idle_inhibitor, on_destroy); 32 | 33 | if (manager) { 34 | /* 35 | * The display destroy event might have been triggered 36 | * already and thus the manager would be NULL. 37 | */ 38 | bool still_inhibited = 39 | wl_list_length(&manager->inhibitor.manager->inhibitors) > 1; 40 | wlr_idle_notifier_v1_set_inhibited(manager->ext, still_inhibited); 41 | } 42 | 43 | wl_list_remove(&idle_inhibitor->on_destroy.link); 44 | free(idle_inhibitor); 45 | } 46 | 47 | static void 48 | handle_idle_inhibitor_new(struct wl_listener *listener, void *data) 49 | { 50 | assert(manager); 51 | struct wlr_idle_inhibitor_v1 *wlr_inhibitor = data; 52 | 53 | struct lab_idle_inhibitor *inhibitor = znew(*inhibitor); 54 | inhibitor->wlr_inhibitor = wlr_inhibitor; 55 | inhibitor->on_destroy.notify = handle_idle_inhibitor_destroy; 56 | wl_signal_add(&wlr_inhibitor->events.destroy, &inhibitor->on_destroy); 57 | 58 | wlr_idle_notifier_v1_set_inhibited(manager->ext, true); 59 | } 60 | 61 | static void 62 | handle_display_destroy(struct wl_listener *listener, void *data) 63 | { 64 | /* 65 | * All the managers will react to the display 66 | * destroy signal as well and thus clean up. 67 | */ 68 | wl_list_remove(&manager->on_display_destroy.link); 69 | zfree(manager); 70 | } 71 | 72 | void 73 | idle_manager_create(struct wl_display *display, struct wlr_seat *wlr_seat) 74 | { 75 | assert(!manager); 76 | manager = znew(*manager); 77 | manager->wlr_seat = wlr_seat; 78 | 79 | manager->ext = wlr_idle_notifier_v1_create(display); 80 | 81 | manager->inhibitor.manager = wlr_idle_inhibit_v1_create(display); 82 | manager->inhibitor.on_new_inhibitor.notify = handle_idle_inhibitor_new; 83 | wl_signal_add(&manager->inhibitor.manager->events.new_inhibitor, 84 | &manager->inhibitor.on_new_inhibitor); 85 | 86 | manager->on_display_destroy.notify = handle_display_destroy; 87 | wl_display_add_destroy_listener(display, &manager->on_display_destroy); 88 | } 89 | 90 | void 91 | idle_manager_notify_activity(struct wlr_seat *seat) 92 | { 93 | /* 94 | * The display destroy event might have been triggered 95 | * already and thus the manager would be NULL. Due to 96 | * future code changes we might also get called before 97 | * the manager has been created. 98 | */ 99 | if (!manager) { 100 | return; 101 | } 102 | 103 | wlr_idle_notifier_v1_notify_activity(manager->ext, seat); 104 | } 105 | -------------------------------------------------------------------------------- /src/decorations/xdg-deco.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include "common/mem.h" 4 | #include "decorations.h" 5 | #include "labwc.h" 6 | #include "view.h" 7 | 8 | struct xdg_deco { 9 | struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_decoration; 10 | struct view *view; 11 | struct wl_listener destroy; 12 | struct wl_listener request_mode; 13 | }; 14 | 15 | static void 16 | xdg_deco_destroy(struct wl_listener *listener, void *data) 17 | { 18 | struct xdg_deco *xdg_deco = wl_container_of(listener, xdg_deco, destroy); 19 | wl_list_remove(&xdg_deco->destroy.link); 20 | wl_list_remove(&xdg_deco->request_mode.link); 21 | free(xdg_deco); 22 | } 23 | 24 | static void 25 | xdg_deco_request_mode(struct wl_listener *listener, void *data) 26 | { 27 | struct xdg_deco *xdg_deco = wl_container_of(listener, xdg_deco, request_mode); 28 | enum wlr_xdg_toplevel_decoration_v1_mode client_mode = 29 | xdg_deco->wlr_xdg_decoration->requested_mode; 30 | 31 | switch (client_mode) { 32 | case WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: 33 | xdg_deco->view->ssd_preference = LAB_SSD_PREF_SERVER; 34 | break; 35 | case WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: 36 | xdg_deco->view->ssd_preference = LAB_SSD_PREF_CLIENT; 37 | break; 38 | case WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_NONE: 39 | xdg_deco->view->ssd_preference = LAB_SSD_PREF_UNSPEC; 40 | client_mode = rc.xdg_shell_server_side_deco 41 | ? WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE 42 | : WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; 43 | break; 44 | default: 45 | wlr_log(WLR_ERROR, "Unspecified xdg decoration variant " 46 | "requested: %u", client_mode); 47 | } 48 | 49 | wlr_xdg_toplevel_decoration_v1_set_mode(xdg_deco->wlr_xdg_decoration, 50 | client_mode); 51 | view_set_decorations(xdg_deco->view, 52 | client_mode == WLR_XDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE); 53 | } 54 | 55 | static void 56 | xdg_toplevel_decoration(struct wl_listener *listener, void *data) 57 | { 58 | struct wlr_xdg_toplevel_decoration_v1 *wlr_xdg_decoration = data; 59 | struct wlr_xdg_surface *xdg_surface = wlr_xdg_decoration->toplevel->base; 60 | if (!xdg_surface || !xdg_surface->data) { 61 | wlr_log(WLR_ERROR, 62 | "Invalid surface supplied for xdg decorations"); 63 | return; 64 | } 65 | 66 | struct xdg_deco *xdg_deco = znew(*xdg_deco); 67 | xdg_deco->wlr_xdg_decoration = wlr_xdg_decoration; 68 | xdg_deco->view = (struct view *)xdg_surface->data; 69 | 70 | wl_signal_add(&wlr_xdg_decoration->events.destroy, &xdg_deco->destroy); 71 | xdg_deco->destroy.notify = xdg_deco_destroy; 72 | 73 | wl_signal_add(&wlr_xdg_decoration->events.request_mode, 74 | &xdg_deco->request_mode); 75 | xdg_deco->request_mode.notify = xdg_deco_request_mode; 76 | 77 | xdg_deco_request_mode(&xdg_deco->request_mode, wlr_xdg_decoration); 78 | } 79 | 80 | void 81 | xdg_server_decoration_init(struct server *server) 82 | { 83 | struct wlr_xdg_decoration_manager_v1 *xdg_deco_mgr = NULL; 84 | xdg_deco_mgr = wlr_xdg_decoration_manager_v1_create(server->wl_display); 85 | if (!xdg_deco_mgr) { 86 | wlr_log(WLR_ERROR, "unable to create the XDG deco manager"); 87 | exit(EXIT_FAILURE); 88 | } 89 | 90 | wl_signal_add(&xdg_deco_mgr->events.new_toplevel_decoration, 91 | &server->xdg_toplevel_decoration); 92 | server->xdg_toplevel_decoration.notify = xdg_toplevel_decoration; 93 | } 94 | -------------------------------------------------------------------------------- /protocols/wlr-input-inhibitor-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2018 Drew DeVault 5 | 6 | Permission to use, copy, modify, distribute, and sell this 7 | software and its documentation for any purpose is hereby granted 8 | without fee, provided that the above copyright notice appear in 9 | all copies and that both that copyright notice and this permission 10 | notice appear in supporting documentation, and that the name of 11 | the copyright holders not be used in advertising or publicity 12 | pertaining to distribution of the software without specific, 13 | written prior permission. The copyright holders make no 14 | representations about the suitability of this software for any 15 | purpose. It is provided "as is" without express or implied 16 | warranty. 17 | 18 | THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS 19 | SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 20 | FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 23 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 24 | ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 25 | THIS SOFTWARE. 26 | 27 | 28 | 29 | 30 | Clients can use this interface to prevent input events from being sent to 31 | any surfaces but its own, which is useful for example in lock screen 32 | software. It is assumed that access to this interface will be locked down 33 | to whitelisted clients by the compositor. 34 | 35 | 36 | 37 | 38 | Activates the input inhibitor. As long as the inhibitor is active, the 39 | compositor will not send input events to other clients. 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | While this resource exists, input to clients other than the owner of the 52 | inhibitor resource will not receive input events. The client that owns 53 | this resource will receive all input events normally. The compositor will 54 | also disable all of its own input processing (such as keyboard shortcuts) 55 | while the inhibitor is active. 56 | 57 | The compositor may continue to send input events to selected clients, 58 | such as an on-screen keyboard (via the input-method protocol). 59 | 60 | 61 | 62 | 63 | Destroy the inhibitor and allow other clients to receive input. 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/input/gestures.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include "input/gestures.h" 4 | #include "labwc.h" 5 | 6 | static void 7 | handle_pointer_pinch_begin(struct wl_listener *listener, void *data) 8 | { 9 | struct seat *seat = wl_container_of(listener, seat, pinch_begin); 10 | struct wlr_pointer_pinch_begin_event *event = data; 11 | wlr_pointer_gestures_v1_send_pinch_begin(seat->pointer_gestures, 12 | seat->seat, event->time_msec, event->fingers); 13 | } 14 | 15 | static void 16 | handle_pointer_pinch_update(struct wl_listener *listener, void *data) 17 | { 18 | struct seat *seat = wl_container_of(listener, seat, pinch_update); 19 | struct wlr_pointer_pinch_update_event *event = data; 20 | wlr_pointer_gestures_v1_send_pinch_update(seat->pointer_gestures, 21 | seat->seat, event->time_msec, event->dx, event->dy, 22 | event->scale, event->rotation); 23 | } 24 | 25 | static void 26 | handle_pointer_pinch_end(struct wl_listener *listener, void *data) 27 | { 28 | struct seat *seat = wl_container_of(listener, seat, pinch_end); 29 | struct wlr_pointer_pinch_end_event *event = data; 30 | wlr_pointer_gestures_v1_send_pinch_end(seat->pointer_gestures, 31 | seat->seat, event->time_msec, event->cancelled); 32 | } 33 | 34 | static void 35 | handle_pointer_swipe_begin(struct wl_listener *listener, void *data) 36 | { 37 | struct seat *seat = wl_container_of(listener, seat, swipe_begin); 38 | struct wlr_pointer_swipe_begin_event *event = data; 39 | wlr_pointer_gestures_v1_send_swipe_begin(seat->pointer_gestures, 40 | seat->seat, event->time_msec, event->fingers); 41 | } 42 | 43 | static void 44 | handle_pointer_swipe_update(struct wl_listener *listener, void *data) 45 | { 46 | struct seat *seat = wl_container_of(listener, seat, swipe_update); 47 | struct wlr_pointer_swipe_update_event *event = data; 48 | wlr_pointer_gestures_v1_send_swipe_update(seat->pointer_gestures, 49 | seat->seat, event->time_msec, event->dx, event->dy); 50 | } 51 | 52 | static void 53 | handle_pointer_swipe_end(struct wl_listener *listener, void *data) 54 | { 55 | struct seat *seat = wl_container_of(listener, seat, swipe_end); 56 | struct wlr_pointer_swipe_end_event *event = data; 57 | wlr_pointer_gestures_v1_send_swipe_end(seat->pointer_gestures, 58 | seat->seat, event->time_msec, event->cancelled); 59 | } 60 | 61 | void 62 | gestures_init(struct seat *seat) 63 | { 64 | seat->pointer_gestures = wlr_pointer_gestures_v1_create(seat->server->wl_display); 65 | 66 | seat->pinch_begin.notify = handle_pointer_pinch_begin; 67 | wl_signal_add(&seat->cursor->events.pinch_begin, &seat->pinch_begin); 68 | 69 | seat->pinch_update.notify = handle_pointer_pinch_update; 70 | wl_signal_add(&seat->cursor->events.pinch_update, &seat->pinch_update); 71 | 72 | seat->pinch_end.notify = handle_pointer_pinch_end; 73 | wl_signal_add(&seat->cursor->events.pinch_end, &seat->pinch_end); 74 | 75 | seat->swipe_begin.notify = handle_pointer_swipe_begin; 76 | wl_signal_add(&seat->cursor->events.swipe_begin, &seat->swipe_begin); 77 | 78 | seat->swipe_update.notify = handle_pointer_swipe_update; 79 | wl_signal_add(&seat->cursor->events.swipe_update, &seat->swipe_update); 80 | 81 | seat->swipe_end.notify = handle_pointer_swipe_end; 82 | wl_signal_add(&seat->cursor->events.swipe_end, &seat->swipe_end); 83 | } 84 | 85 | void 86 | gestures_finish(struct seat *seat) 87 | { 88 | wl_list_remove(&seat->pinch_begin.link); 89 | wl_list_remove(&seat->pinch_update.link); 90 | wl_list_remove(&seat->pinch_end.link); 91 | wl_list_remove(&seat->swipe_begin.link); 92 | wl_list_remove(&seat->swipe_update.link); 93 | wl_list_remove(&seat->swipe_end.link); 94 | } 95 | -------------------------------------------------------------------------------- /src/common/dir.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Find the configuration and theme directories 4 | * 5 | * Copyright Johan Malm 2020 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "common/dir.h" 14 | 15 | struct dir { 16 | const char *prefix; 17 | const char *path; 18 | }; 19 | 20 | static struct dir config_dirs[] = { 21 | { "XDG_CONFIG_HOME", "labwc" }, 22 | { "HOME", ".config/labwc" }, 23 | { "XDG_CONFIG_DIRS", "labwc" }, 24 | { NULL, "/etc/xdg/labwc" }, 25 | { NULL, NULL } 26 | }; 27 | 28 | static struct dir theme_dirs[] = { 29 | { "XDG_DATA_HOME", "themes" }, 30 | { "HOME", ".local/share/themes" }, 31 | { "HOME", ".themes" }, 32 | { "XDG_DATA_DIRS", "themes" }, 33 | { NULL, "/usr/share/themes" }, 34 | { NULL, "/usr/local/share/themes" }, 35 | { NULL, "/opt/share/themes" }, 36 | { NULL, NULL } 37 | }; 38 | 39 | static bool 40 | isdir(const char *path) 41 | { 42 | struct stat st; 43 | return (!stat(path, &st) && S_ISDIR(st.st_mode)); 44 | } 45 | 46 | struct ctx { 47 | void (*build_path_fn)(struct ctx *ctx, char *prefix, const char *path); 48 | char *buf; 49 | size_t len; 50 | struct dir *dirs; 51 | const char *theme_name; 52 | }; 53 | 54 | static void 55 | build_config_path(struct ctx *ctx, char *prefix, const char *path) 56 | { 57 | if (!prefix) { 58 | snprintf(ctx->buf, ctx->len, "%s", path); 59 | } else { 60 | snprintf(ctx->buf, ctx->len, "%s/%s", prefix, path); 61 | } 62 | } 63 | 64 | static void 65 | build_theme_path(struct ctx *ctx, char *prefix, const char *path) 66 | { 67 | if (!prefix) { 68 | snprintf(ctx->buf, ctx->len, "%s/%s/openbox-3", path, 69 | ctx->theme_name); 70 | } else { 71 | snprintf(ctx->buf, ctx->len, "%s/%s/%s/openbox-3", prefix, path, 72 | ctx->theme_name); 73 | } 74 | } 75 | 76 | static char * 77 | find_dir(struct ctx *ctx) 78 | { 79 | char *debug = getenv("LABWC_DEBUG_DIR_CONFIG_AND_THEME"); 80 | 81 | for (int i = 0; ctx->dirs[i].path; i++) { 82 | struct dir d = ctx->dirs[i]; 83 | if (!d.prefix) { 84 | /* handle /etc/xdg... */ 85 | ctx->build_path_fn(ctx, NULL, d.path); 86 | if (debug) { 87 | fprintf(stderr, "%s\n", ctx->buf); 88 | } 89 | if (isdir(ctx->buf)) { 90 | return ctx->buf; 91 | } 92 | } else { 93 | /* handle $HOME/.config/... and $XDG_* */ 94 | char *prefix = getenv(d.prefix); 95 | if (!prefix) { 96 | continue; 97 | } 98 | gchar * *prefixes; 99 | prefixes = g_strsplit(prefix, ":", -1); 100 | for (gchar * *p = prefixes; *p; p++) { 101 | ctx->build_path_fn(ctx, *p, d.path); 102 | if (debug) { 103 | fprintf(stderr, "%s\n", ctx->buf); 104 | } 105 | if (isdir(ctx->buf)) { 106 | g_strfreev(prefixes); 107 | return ctx->buf; 108 | } 109 | } 110 | g_strfreev(prefixes); 111 | } 112 | } 113 | /* no directory was found */ 114 | ctx->buf[0] = '\0'; 115 | return ctx->buf; 116 | } 117 | 118 | char * 119 | config_dir(void) 120 | { 121 | static char buf[4096] = { 0 }; 122 | if (buf[0] != '\0') { 123 | return buf; 124 | } 125 | struct ctx ctx = { 126 | .build_path_fn = build_config_path, 127 | .buf = buf, 128 | .len = sizeof(buf), 129 | .dirs = config_dirs 130 | }; 131 | return find_dir(&ctx); 132 | } 133 | 134 | char * 135 | theme_dir(const char *theme_name) 136 | { 137 | static char buf[4096] = { 0 }; 138 | struct ctx ctx = { 139 | .build_path_fn = build_theme_path, 140 | .buf = buf, 141 | .len = sizeof(buf), 142 | .dirs = theme_dirs, 143 | .theme_name = theme_name 144 | }; 145 | return find_dir(&ctx); 146 | } 147 | -------------------------------------------------------------------------------- /src/xdg-popup.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Copyright (C) 2020 the sway authors 4 | * 5 | * This file is only needed in support of 6 | * - unconstraining XDG popups 7 | * - keeping non-layer-shell xdg-popups outside the layers.c code 8 | */ 9 | 10 | #include "common/mem.h" 11 | #include "labwc.h" 12 | #include "node.h" 13 | #include "view.h" 14 | 15 | struct xdg_popup { 16 | struct view *parent_view; 17 | struct wlr_xdg_popup *wlr_popup; 18 | 19 | struct wl_listener destroy; 20 | struct wl_listener new_popup; 21 | }; 22 | 23 | static void 24 | popup_unconstrain(struct view *view, struct wlr_xdg_popup *popup) 25 | { 26 | struct server *server = view->server; 27 | struct wlr_box *popup_box = &popup->current.geometry; 28 | struct wlr_output_layout *output_layout = server->output_layout; 29 | struct wlr_output *wlr_output = wlr_output_layout_output_at( 30 | output_layout, view->current.x + popup_box->x, 31 | view->current.y + popup_box->y); 32 | 33 | struct wlr_box output_box; 34 | wlr_output_layout_get_box(output_layout, wlr_output, &output_box); 35 | 36 | struct wlr_box output_toplevel_box = { 37 | .x = output_box.x - view->current.x, 38 | .y = output_box.y - view->current.y, 39 | .width = output_box.width, 40 | .height = output_box.height, 41 | }; 42 | wlr_xdg_popup_unconstrain_from_box(popup, &output_toplevel_box); 43 | } 44 | 45 | static void 46 | handle_xdg_popup_destroy(struct wl_listener *listener, void *data) 47 | { 48 | struct xdg_popup *popup = wl_container_of(listener, popup, destroy); 49 | wl_list_remove(&popup->destroy.link); 50 | wl_list_remove(&popup->new_popup.link); 51 | free(popup); 52 | } 53 | 54 | static void 55 | popup_handle_new_xdg_popup(struct wl_listener *listener, void *data) 56 | { 57 | struct xdg_popup *popup = wl_container_of(listener, popup, new_popup); 58 | struct wlr_xdg_popup *wlr_popup = data; 59 | xdg_popup_create(popup->parent_view, wlr_popup); 60 | } 61 | 62 | void 63 | xdg_popup_create(struct view *view, struct wlr_xdg_popup *wlr_popup) 64 | { 65 | struct wlr_xdg_surface *parent = 66 | wlr_xdg_surface_try_from_wlr_surface(wlr_popup->parent); 67 | if (!parent) { 68 | wlr_log(WLR_ERROR, "parent is not a valid XDG surface"); 69 | return; 70 | } 71 | 72 | struct xdg_popup *popup = znew(*popup); 73 | popup->parent_view = view; 74 | popup->wlr_popup = wlr_popup; 75 | 76 | popup->destroy.notify = handle_xdg_popup_destroy; 77 | wl_signal_add(&wlr_popup->base->events.destroy, &popup->destroy); 78 | popup->new_popup.notify = popup_handle_new_xdg_popup; 79 | wl_signal_add(&wlr_popup->base->events.new_popup, &popup->new_popup); 80 | 81 | /* 82 | * We must add xdg popups to the scene graph so they get rendered. The 83 | * wlroots scene graph provides a helper for this, but to use it we must 84 | * provide the proper parent scene node of the xdg popup. To enable 85 | * this, we always set the user data field of xdg_surfaces to the 86 | * corresponding scene node. 87 | * 88 | * xdg-popups live in server->xdg_popup_tree so that they can be 89 | * rendered above always-on-top windows 90 | */ 91 | struct wlr_scene_tree *parent_tree = NULL; 92 | if (parent->role == WLR_XDG_SURFACE_ROLE_POPUP) { 93 | parent_tree = parent->surface->data; 94 | } else { 95 | parent_tree = view->server->xdg_popup_tree; 96 | wlr_scene_node_set_position(&view->server->xdg_popup_tree->node, 97 | view->current.x, view->current.y); 98 | } 99 | wlr_popup->base->surface->data = 100 | wlr_scene_xdg_surface_create(parent_tree, wlr_popup->base); 101 | node_descriptor_create(wlr_popup->base->surface->data, 102 | LAB_NODE_DESC_XDG_POPUP, view); 103 | 104 | popup_unconstrain(view, wlr_popup); 105 | } 106 | -------------------------------------------------------------------------------- /src/input/key-state.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "input/key-state.h" 9 | 10 | #define MAX_PRESSED_KEYS (16) 11 | 12 | struct key_array { 13 | uint32_t keys[MAX_PRESSED_KEYS]; 14 | int nr_keys; 15 | }; 16 | 17 | static struct key_array pressed, pressed_mods, bound, pressed_sent; 18 | 19 | static void 20 | report(struct key_array *array, const char *msg) 21 | { 22 | static char *should_print; 23 | static bool has_run; 24 | 25 | if (!has_run) { 26 | should_print = getenv("LABWC_DEBUG_KEY_STATE"); 27 | has_run = true; 28 | } 29 | if (!should_print) { 30 | return; 31 | } 32 | printf("%s", msg); 33 | for (int i = 0; i < array->nr_keys; ++i) { 34 | printf("%d,", array->keys[i]); 35 | } 36 | printf("\n"); 37 | } 38 | 39 | static bool 40 | key_present(struct key_array *array, uint32_t keycode) 41 | { 42 | for (int i = 0; i < array->nr_keys; ++i) { 43 | if (array->keys[i] == keycode) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | } 49 | 50 | static void 51 | remove_key(struct key_array *array, uint32_t keycode) 52 | { 53 | bool shifting = false; 54 | 55 | for (int i = 0; i < MAX_PRESSED_KEYS; ++i) { 56 | if (array->keys[i] == keycode) { 57 | --array->nr_keys; 58 | shifting = true; 59 | } 60 | if (shifting) { 61 | array->keys[i] = i < MAX_PRESSED_KEYS - 1 62 | ? array->keys[i + 1] : 0; 63 | } 64 | } 65 | } 66 | 67 | static void 68 | add_key(struct key_array *array, uint32_t keycode) 69 | { 70 | if (!key_present(array, keycode) && array->nr_keys < MAX_PRESSED_KEYS) { 71 | array->keys[array->nr_keys++] = keycode; 72 | } 73 | } 74 | 75 | uint32_t * 76 | key_state_pressed_sent_keycodes(void) 77 | { 78 | report(&pressed, "before - pressed:"); 79 | report(&bound, "before - bound:"); 80 | 81 | /* pressed_sent = pressed - bound */ 82 | memcpy(pressed_sent.keys, pressed.keys, 83 | MAX_PRESSED_KEYS * sizeof(uint32_t)); 84 | pressed_sent.nr_keys = pressed.nr_keys; 85 | for (int i = 0; i < bound.nr_keys; ++i) { 86 | remove_key(&pressed_sent, bound.keys[i]); 87 | } 88 | 89 | report(&pressed_sent, "after - pressed_sent:"); 90 | 91 | return pressed_sent.keys; 92 | } 93 | 94 | int 95 | key_state_nr_pressed_sent_keycodes(void) 96 | { 97 | return pressed_sent.nr_keys; 98 | } 99 | 100 | void 101 | key_state_set_pressed(uint32_t keycode, bool is_pressed, bool is_modifier) 102 | { 103 | if (is_pressed) { 104 | add_key(&pressed, keycode); 105 | if (is_modifier) { 106 | add_key(&pressed_mods, keycode); 107 | } 108 | } else { 109 | remove_key(&pressed, keycode); 110 | remove_key(&pressed_mods, keycode); 111 | } 112 | } 113 | 114 | void 115 | key_state_store_pressed_key_as_bound(uint32_t keycode) 116 | { 117 | add_key(&bound, keycode); 118 | /* 119 | * Also store any pressed modifiers as bound. This prevents 120 | * applications from seeing and handling the release event for 121 | * a modifier key that was part of a keybinding (e.g. Firefox 122 | * displays its menu bar for a lone Alt press + release). 123 | */ 124 | for (int i = 0; i < pressed_mods.nr_keys; ++i) { 125 | add_key(&bound, pressed_mods.keys[i]); 126 | } 127 | } 128 | 129 | bool 130 | key_state_corresponding_press_event_was_bound(uint32_t keycode) 131 | { 132 | return key_present(&bound, keycode); 133 | } 134 | 135 | void 136 | key_state_bound_key_remove(uint32_t keycode) 137 | { 138 | remove_key(&bound, keycode); 139 | } 140 | 141 | int 142 | key_state_nr_bound_keys(void) 143 | { 144 | return bound.nr_keys; 145 | } 146 | 147 | int 148 | key_state_nr_pressed_keys(void) 149 | { 150 | return pressed.nr_keys; 151 | } 152 | -------------------------------------------------------------------------------- /include/menu/menu.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_MENU_H 3 | #define LABWC_MENU_H 4 | 5 | #include 6 | 7 | /* forward declare arguments */ 8 | struct view; 9 | struct server; 10 | struct wl_list; 11 | struct wlr_scene_tree; 12 | struct wlr_scene_node; 13 | struct scaled_font_buffer; 14 | 15 | enum menu_align { 16 | LAB_MENU_OPEN_AUTO = 0, 17 | LAB_MENU_OPEN_LEFT = 1 << 0, 18 | LAB_MENU_OPEN_RIGHT = 1 << 1, 19 | LAB_MENU_OPEN_TOP = 1 << 2, 20 | LAB_MENU_OPEN_BOTTOM = 1 << 3, 21 | }; 22 | 23 | struct menu_scene { 24 | struct wlr_scene_tree *tree; 25 | struct wlr_scene_node *text; 26 | struct wlr_scene_node *background; 27 | struct scaled_font_buffer *buffer; 28 | }; 29 | 30 | struct menuitem { 31 | struct wl_list actions; 32 | struct menu *parent; 33 | struct menu *submenu; 34 | bool selectable; 35 | int height; 36 | int native_width; 37 | struct wlr_scene_tree *tree; 38 | struct menu_scene normal; 39 | struct menu_scene selected; 40 | struct wl_list link; /* menu.menuitems */ 41 | }; 42 | 43 | /* This could be the root-menu or a submenu */ 44 | struct menu { 45 | char *id; 46 | char *label; 47 | int item_height; 48 | struct menu *parent; 49 | struct { 50 | int width; 51 | int height; 52 | } size; 53 | struct wl_list menuitems; 54 | struct server *server; 55 | struct { 56 | struct menu *menu; 57 | struct menuitem *item; 58 | } selection; 59 | struct wlr_scene_tree *scene_tree; 60 | 61 | /* Used to match a window-menu to the view that triggered it. */ 62 | struct view *triggered_by_view; /* may be NULL */ 63 | struct wl_list link; /* server.menus */ 64 | }; 65 | 66 | /* For keyboard support */ 67 | void menu_item_select_next(struct server *server); 68 | void menu_item_select_previous(struct server *server); 69 | void menu_submenu_enter(struct server *server); 70 | void menu_submenu_leave(struct server *server); 71 | bool menu_call_selected_actions(struct server *server); 72 | 73 | void menu_init(struct server *server); 74 | void menu_finish(struct server *server); 75 | 76 | /** 77 | * menu_get_by_id - get menu by id 78 | * 79 | * @id id string defined in menu.xml like "root-menu" 80 | */ 81 | struct menu *menu_get_by_id(struct server *server, const char *id); 82 | 83 | /** 84 | * menu_open - open menu on position (x, y) 85 | * 86 | * This function will close server->menu_current, open the 87 | * new menu and assign @menu to server->menu_current. 88 | * 89 | * Additionally, server->input_mode wil be set to LAB_INPUT_STATE_MENU. 90 | */ 91 | void menu_open(struct menu *menu, int x, int y); 92 | 93 | /** 94 | * menu_process_cursor_motion 95 | * 96 | * - handles hover effects 97 | * - may open/close submenus 98 | */ 99 | void menu_process_cursor_motion(struct wlr_scene_node *node); 100 | 101 | /** 102 | * menu_call_actions - call actions associated with a menu node 103 | * 104 | * If menuitem connected to @node does not just open a submenu: 105 | * - associated actions will be called 106 | * - server->menu_current will be closed 107 | * - server->menu_current will be set to NULL 108 | * 109 | * Returns true if actions have actually been executed 110 | */ 111 | bool menu_call_actions(struct wlr_scene_node *node); 112 | 113 | /** 114 | * menu_close_root- close root menu 115 | * 116 | * This function will close server->menu_current and set it to NULL. 117 | * Asserts that server->input_mode is set to LAB_INPUT_STATE_MENU. 118 | * 119 | * Additionally, server->input_mode wil be set to LAB_INPUT_STATE_PASSTHROUGH. 120 | */ 121 | void menu_close_root(struct server *server); 122 | 123 | /* menu_reconfigure - reload theme and content */ 124 | void menu_reconfigure(struct server *server); 125 | 126 | #endif /* LABWC_MENU_H */ 127 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'labwc', 3 | 'c', 4 | version: '0.6.6', 5 | license: 'GPL-2.0-only', 6 | meson_version: '>=0.59.0', 7 | default_options: [ 8 | 'c_std=c11', 9 | 'warning_level=2', 10 | ], 11 | ) 12 | 13 | add_project_arguments( 14 | [ 15 | '-DWLR_USE_UNSTABLE', 16 | ], 17 | language: 'c', 18 | ) 19 | 20 | cc = meson.get_compiler('c') 21 | 22 | add_project_arguments(cc.get_supported_arguments([ 23 | '-Wundef', 24 | '-Wlogical-op', 25 | '-Wmissing-include-dirs', 26 | '-Wold-style-definition', 27 | '-Wpointer-arith', 28 | '-Winit-self', 29 | '-Wstrict-prototypes', 30 | '-Wimplicit-fallthrough=2', 31 | '-Wendif-labels', 32 | '-Wstrict-aliasing=2', 33 | '-Woverflow', 34 | '-Wmissing-prototypes', 35 | '-Walloca', 36 | '-Wunused-macros', 37 | 38 | '-Wno-unused-parameter', 39 | ]), language: 'c') 40 | 41 | version='"@0@"'.format(meson.project_version()) 42 | git = find_program('git', native: true, required: false) 43 | if git.found() 44 | git_commit = run_command([git, 'describe', '--dirty'], check: false) 45 | if git_commit.returncode() == 0 46 | version = '"@0@"'.format(git_commit.stdout().strip()) 47 | endif 48 | endif 49 | add_project_arguments('-DLABWC_VERSION=@0@'.format(version), language: 'c') 50 | 51 | wlroots = dependency( 52 | 'wlroots', 53 | default_options: ['default_library=static', 'examples=false'], 54 | version: ['>=0.17.0', '<0.18.0'], 55 | ) 56 | 57 | wlroots_has_xwayland = wlroots.get_variable('have_xwayland') == 'true' 58 | 59 | wayland_server = dependency('wayland-server', version: '>=1.19.0') 60 | wayland_protos = dependency('wayland-protocols') 61 | xkbcommon = dependency('xkbcommon') 62 | xcb = dependency('xcb', required: get_option('xwayland')) 63 | xcb_icccm = dependency('xcb-icccm', required: get_option('xwayland')) 64 | drm_full = dependency('libdrm') 65 | drm = drm_full.partial_dependency(compile_args: true, includes: true) 66 | xml2 = dependency('libxml-2.0') 67 | glib = dependency('glib-2.0') 68 | cairo = dependency('cairo') 69 | pangocairo = dependency('pangocairo') 70 | input = dependency('libinput', version: '>=1.14') 71 | pixman = dependency('pixman-1') 72 | math = cc.find_library('m') 73 | png = dependency('libpng') 74 | svg = dependency('librsvg-2.0', version: '>=2.46', required: false) 75 | 76 | if get_option('xwayland').enabled() and not wlroots_has_xwayland 77 | error('no wlroots Xwayland support') 78 | endif 79 | have_xwayland = xcb.found() and wlroots_has_xwayland 80 | conf_data = configuration_data() 81 | conf_data.set10('HAVE_XWAYLAND', have_xwayland) 82 | 83 | if get_option('svg').disabled() 84 | have_rsvg = false 85 | else 86 | have_rsvg = svg.found() 87 | endif 88 | conf_data.set10('HAVE_RSVG', have_rsvg) 89 | 90 | msgfmt = find_program('msgfmt', required: get_option('nls')) 91 | if msgfmt.found() 92 | source_root = meson.current_source_dir() 93 | conf_data.set('HAVE_NLS', 1) 94 | subdir('po') 95 | else 96 | conf_data.set('HAVE_NLS', 0) 97 | endif 98 | 99 | labwc_inc = include_directories('include') 100 | 101 | subdir('protocols') 102 | 103 | labwc_deps = [ 104 | server_protos, 105 | wayland_server, 106 | wlroots, 107 | xkbcommon, 108 | xcb_icccm, 109 | xml2, 110 | glib, 111 | cairo, 112 | drm, 113 | pangocairo, 114 | input, 115 | pixman, 116 | math, 117 | png, 118 | ] 119 | if have_rsvg 120 | labwc_deps += [ 121 | svg, 122 | ] 123 | endif 124 | 125 | subdir('include') 126 | subdir('src') 127 | subdir('docs') 128 | 129 | executable( 130 | meson.project_name(), 131 | labwc_sources, 132 | include_directories: [labwc_inc], 133 | dependencies: labwc_deps, 134 | install: true, 135 | ) 136 | 137 | install_data('docs/labwc.desktop', install_dir: get_option('datadir') / 'wayland-sessions') 138 | -------------------------------------------------------------------------------- /src/view-impl-common.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* view-impl-common.c: common code for shell view->impl functions */ 3 | #include 4 | #include 5 | #include 6 | #include "common/list.h" 7 | #include "labwc.h" 8 | #include "view.h" 9 | #include "view-impl-common.h" 10 | #include "window-rules.h" 11 | 12 | void 13 | view_impl_move_to_front(struct view *view) 14 | { 15 | wl_list_remove(&view->link); 16 | wl_list_insert(&view->server->views, &view->link); 17 | wlr_scene_node_raise_to_top(&view->scene_tree->node); 18 | } 19 | 20 | void 21 | view_impl_move_to_back(struct view *view) 22 | { 23 | wl_list_remove(&view->link); 24 | wl_list_append(&view->server->views, &view->link); 25 | wlr_scene_node_lower_to_bottom(&view->scene_tree->node); 26 | } 27 | 28 | void 29 | view_impl_map(struct view *view) 30 | { 31 | desktop_focus_view(view, /*raise*/ true); 32 | view_update_title(view); 33 | view_update_app_id(view); 34 | if (!view->been_mapped) { 35 | window_rules_apply(view, LAB_WINDOW_RULE_EVENT_ON_FIRST_MAP); 36 | } 37 | 38 | /* 39 | * It's tempting to just never create the foreign-toplevel handle in the 40 | * map handlers, but the app_id/title might not have been set at that 41 | * point, so it's safer to process the property here 42 | */ 43 | enum property ret = window_rules_get_property(view, "skipTaskbar"); 44 | if (ret == LAB_PROP_TRUE) { 45 | if (view->toplevel.handle) { 46 | wlr_foreign_toplevel_handle_v1_destroy(view->toplevel.handle); 47 | } 48 | } 49 | 50 | wlr_log(WLR_DEBUG, "[map] identifier=%s, title=%s\n", 51 | view_get_string_prop(view, "app_id"), 52 | view_get_string_prop(view, "title")); 53 | } 54 | 55 | void 56 | view_impl_unmap(struct view *view) 57 | { 58 | struct server *server = view->server; 59 | if (view == server->active_view) { 60 | desktop_focus_topmost_view(server); 61 | } 62 | if (view == server->last_raised_view) { 63 | server->last_raised_view = NULL; 64 | } 65 | } 66 | 67 | static bool 68 | resizing_edge(struct view *view, uint32_t edge) 69 | { 70 | struct server *server = view->server; 71 | return server->input_mode == LAB_INPUT_STATE_RESIZE 72 | && server->grabbed_view == view 73 | && (server->resize_edges & edge); 74 | } 75 | 76 | void 77 | view_impl_apply_geometry(struct view *view, int w, int h) 78 | { 79 | struct wlr_box *current = &view->current; 80 | struct wlr_box *pending = &view->pending; 81 | struct wlr_box old = *current; 82 | 83 | /* 84 | * Anchor right edge if resizing via left edge. 85 | * 86 | * Note that answering the question "are we resizing?" is a bit 87 | * tricky. The most obvious method is to look at the server 88 | * flags; but that method will not account for any late commits 89 | * that occur after the mouse button is released, as the client 90 | * catches up with pending configure requests. So as a fallback, 91 | * we resort to a geometry-based heuristic -- also not 100% 92 | * reliable on its own. The combination of the two methods 93 | * should catch 99% of resize cases that we care about. 94 | */ 95 | bool resizing_left_edge = resizing_edge(view, WLR_EDGE_LEFT); 96 | if (resizing_left_edge || (current->x != pending->x 97 | && current->x + current->width == 98 | pending->x + pending->width)) { 99 | current->x = pending->x + pending->width - w; 100 | } else { 101 | current->x = pending->x; 102 | } 103 | 104 | /* Anchor bottom edge if resizing via top edge */ 105 | bool resizing_top_edge = resizing_edge(view, WLR_EDGE_TOP); 106 | if (resizing_top_edge || (current->y != pending->y 107 | && current->y + current->height == 108 | pending->y + pending->height)) { 109 | current->y = pending->y + pending->height - h; 110 | } else { 111 | current->y = pending->y; 112 | } 113 | 114 | current->width = w; 115 | current->height = h; 116 | 117 | if (!wlr_box_equal(current, &old)) { 118 | view_moved(view); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/dnd.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "common/mem.h" 7 | #include "dnd.h" 8 | #include "input/cursor.h" 9 | #include "labwc.h" /* for struct seat */ 10 | #include "view.h" 11 | 12 | /* Internal DnD handlers */ 13 | static void 14 | handle_drag_request(struct wl_listener *listener, void *data) 15 | { 16 | struct seat *seat = wl_container_of(listener, seat, drag.events.request); 17 | struct wlr_seat_request_start_drag_event *event = data; 18 | 19 | if (wlr_seat_validate_pointer_grab_serial( 20 | seat->seat, event->origin, event->serial)) { 21 | wlr_seat_start_pointer_drag(seat->seat, event->drag, 22 | event->serial); 23 | } else { 24 | wlr_data_source_destroy(event->drag->source); 25 | wlr_log(WLR_ERROR, "wrong source for drag request"); 26 | } 27 | } 28 | 29 | static void 30 | handle_drag_start(struct wl_listener *listener, void *data) 31 | { 32 | struct seat *seat = wl_container_of(listener, seat, drag.events.start); 33 | assert(!seat->drag.active); 34 | struct wlr_drag *drag = data; 35 | 36 | seat->drag.active = true; 37 | seat_reset_pressed(seat); 38 | if (drag->icon) { 39 | /* Cleans up automatically on drag->icon->events.destroy */ 40 | wlr_scene_drag_icon_create(seat->drag.icons, drag->icon); 41 | wlr_scene_node_set_enabled(&seat->drag.icons->node, true); 42 | } 43 | wl_signal_add(&drag->events.destroy, &seat->drag.events.destroy); 44 | } 45 | 46 | static void 47 | handle_drag_destroy(struct wl_listener *listener, void *data) 48 | { 49 | struct seat *seat = wl_container_of(listener, seat, drag.events.destroy); 50 | assert(seat->drag.active); 51 | 52 | seat->drag.active = false; 53 | wl_list_remove(&seat->drag.events.destroy.link); 54 | wlr_scene_node_set_enabled(&seat->drag.icons->node, false); 55 | 56 | /* 57 | * The default focus behaviour at the end of a dnd operation is that the 58 | * window that originally had keyboard-focus retains that focus. This is 59 | * consistent with the default behaviour of openbox and mutter. 60 | * 61 | * However, if the 'focus/followMouse' option is enabled we need to 62 | * refocus the current surface under the cursor because keyboard focus 63 | * is not changed during drag. 64 | */ 65 | if (!rc.focus_follow_mouse) { 66 | return; 67 | } 68 | 69 | struct cursor_context ctx = get_cursor_context(seat->server); 70 | if (!ctx.surface) { 71 | return; 72 | } 73 | seat_focus_surface(seat, NULL); 74 | seat_focus_surface(seat, ctx.surface); 75 | 76 | if (ctx.view && rc.raise_on_focus) { 77 | view_move_to_front(ctx.view); 78 | } 79 | } 80 | 81 | /* Public API */ 82 | void 83 | dnd_init(struct seat *seat) 84 | { 85 | seat->drag.icons = wlr_scene_tree_create(&seat->server->scene->tree); 86 | wlr_scene_node_set_enabled(&seat->drag.icons->node, false); 87 | 88 | seat->drag.events.request.notify = handle_drag_request; 89 | seat->drag.events.start.notify = handle_drag_start; 90 | seat->drag.events.destroy.notify = handle_drag_destroy; 91 | 92 | wl_signal_add(&seat->seat->events.request_start_drag, 93 | &seat->drag.events.request); 94 | wl_signal_add(&seat->seat->events.start_drag, &seat->drag.events.start); 95 | /* 96 | * destroy.notify is listened to in handle_drag_start() and reset in 97 | * handle_drag_destroy() 98 | */ 99 | } 100 | 101 | void 102 | dnd_icons_show(struct seat *seat, bool show) 103 | { 104 | wlr_scene_node_set_enabled(&seat->drag.icons->node, show); 105 | } 106 | 107 | void 108 | dnd_icons_move(struct seat *seat, double x, double y) 109 | { 110 | wlr_scene_node_set_position(&seat->drag.icons->node, x, y); 111 | } 112 | 113 | void dnd_finish(struct seat *seat) 114 | { 115 | wlr_scene_node_destroy(&seat->drag.icons->node); 116 | wl_list_remove(&seat->drag.events.request.link); 117 | wl_list_remove(&seat->drag.events.start.link); 118 | } 119 | -------------------------------------------------------------------------------- /include/common/scaled_scene_buffer.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SCALED_SCENE_BUFFER_H 3 | #define LABWC_SCALED_SCENE_BUFFER_H 4 | 5 | #define LAB_SCALED_BUFFER_MAX_CACHE 2 6 | 7 | struct wl_list; 8 | struct wlr_buffer; 9 | struct wl_listener; 10 | struct wlr_scene_tree; 11 | struct lab_data_buffer; 12 | struct scaled_scene_buffer; 13 | 14 | struct scaled_scene_buffer_impl { 15 | /* Return a new buffer optimized for the new scale */ 16 | struct lab_data_buffer *(*create_buffer) 17 | (struct scaled_scene_buffer *scaled_buffer, double scale); 18 | /* Might be NULL or used for cleaning up */ 19 | void (*destroy)(struct scaled_scene_buffer *scaled_buffer); 20 | }; 21 | 22 | struct scaled_scene_buffer { 23 | struct wlr_scene_buffer *scene_buffer; 24 | int width; /* unscaled, read only */ 25 | int height; /* unscaled, read only */ 26 | void *data; /* opaque user data */ 27 | 28 | /* Private */ 29 | bool drop_buffer; 30 | double active_scale; 31 | struct wl_list cache; /* struct scaled_buffer_cache_entry.link */ 32 | struct wl_listener destroy; 33 | struct wl_listener output_enter; 34 | struct wl_listener output_leave; 35 | const struct scaled_scene_buffer_impl *impl; 36 | }; 37 | 38 | /** 39 | * Create an auto scaling buffer that creates a wlr_scene_buffer 40 | * and subscribes to its output_enter and output_leave signals. 41 | * 42 | * If the maximal scale changes, it either sets an already existing buffer 43 | * that was rendered for the current scale or - if there is none - calls 44 | * implementation->create_buffer(self, scale) to get a new lab_data_buffer 45 | * optimized for the new scale. 46 | * 47 | * Up to LAB_SCALED_BUFFER_MAX_CACHE (2) buffers are cached in an LRU fashion 48 | * to handle the majority of use cases where a view is moved between no more 49 | * than two different scales. 50 | * 51 | * scaled_scene_buffer will clean up automatically once the internal 52 | * wlr_scene_buffer is being destroyed. If implementation->destroy is set 53 | * it will also get called so a consumer of this API may clean up its own 54 | * allocations. 55 | * 56 | * All requested lab_data_buffers via impl->create_buffer() will be locked 57 | * during the lifetime of the buffer in the internal cache and unlocked 58 | * when being evacuated from the cache (due to LAB_SCALED_BUFFER_MAX_CACHE 59 | * or the internal wlr_scene_buffer being destroyed). 60 | * 61 | * If drop_buffer was set during creation of the scaled_scene_buffer, the 62 | * backing wlr_buffer behind a lab_data_buffer will also get dropped 63 | * (via wlr_buffer_drop). If there are no more locks (consumers) of the 64 | * respective buffer this will then cause the lab_data_buffer to be free'd. 65 | * 66 | * In the case of the buffer provider dropping the buffer itself (due to 67 | * for example a Reconfigure event) the lock prevents the buffer from being 68 | * destroyed until the buffer is evacuated from the internal cache and thus 69 | * unlocked. 70 | * 71 | * This allows using scaled_scene_buffer for an autoscaling font_buffer 72 | * (which gets free'd automatically) and also for theme components like 73 | * rounded corner images or button icons whose buffers only exist once but 74 | * are references by multiple windows with their own scaled_scene_buffers. 75 | * 76 | * The rough idea is: use drop_buffer = true for one-shot buffers and false 77 | * for buffers that should outlive the scaled_scene_buffer instance itself. 78 | */ 79 | struct scaled_scene_buffer *scaled_scene_buffer_create( 80 | struct wlr_scene_tree *parent, 81 | const struct scaled_scene_buffer_impl *implementation, 82 | bool drop_buffer); 83 | 84 | /* Clear the cache of existing buffers, useful in case the content changes */ 85 | void scaled_scene_buffer_invalidate_cache(struct scaled_scene_buffer *self); 86 | 87 | /* Private */ 88 | struct scaled_scene_buffer_cache_entry { 89 | struct wl_list link; /* struct scaled_scene_buffer.cache */ 90 | struct wlr_buffer *buffer; 91 | double scale; 92 | }; 93 | 94 | #endif /* LABWC_SCALED_SCENE_BUFFER_H */ 95 | -------------------------------------------------------------------------------- /src/config/session.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #define _POSIX_C_SOURCE 200809L 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "common/buf.h" 11 | #include "common/file-helpers.h" 12 | #include "common/mem.h" 13 | #include "common/spawn.h" 14 | #include "common/string-helpers.h" 15 | #include "config/session.h" 16 | 17 | static bool 18 | string_empty(const char *s) 19 | { 20 | return !s || !*s; 21 | } 22 | 23 | static void 24 | process_line(char *line) 25 | { 26 | if (string_empty(line) || line[0] == '#') { 27 | return; 28 | } 29 | char *key = NULL; 30 | char *p = strchr(line, '='); 31 | if (!p) { 32 | return; 33 | } 34 | *p = '\0'; 35 | key = string_strip(line); 36 | 37 | struct buf value; 38 | buf_init(&value); 39 | buf_add(&value, string_strip(++p)); 40 | buf_expand_shell_variables(&value); 41 | buf_expand_tilde(&value); 42 | if (string_empty(key) || !value.len) { 43 | goto error; 44 | } 45 | setenv(key, value.buf, 1); 46 | error: 47 | free(value.buf); 48 | } 49 | 50 | static void 51 | read_environment_file(const char *filename) 52 | { 53 | char *line = NULL; 54 | size_t len = 0; 55 | FILE *stream = fopen(filename, "r"); 56 | if (!stream) { 57 | return; 58 | } 59 | wlr_log(WLR_INFO, "read environment file %s", filename); 60 | while (getline(&line, &len, stream) != -1) { 61 | char *p = strrchr(line, '\n'); 62 | if (p) { 63 | *p = '\0'; 64 | } 65 | process_line(line); 66 | } 67 | free(line); 68 | fclose(stream); 69 | } 70 | 71 | static char * 72 | build_path(const char *dir, const char *filename) 73 | { 74 | if (string_empty(dir) || string_empty(filename)) { 75 | return NULL; 76 | } 77 | return strdup_printf("%s/%s", dir, filename); 78 | } 79 | 80 | static void 81 | update_activation_env(const char *env_keys) 82 | { 83 | if (!getenv("DBUS_SESSION_BUS_ADDRESS")) { 84 | /* Prevent accidentally auto-launching a dbus session */ 85 | wlr_log(WLR_INFO, "Not updating dbus execution environment: " 86 | "DBUS_SESSION_BUS_ADDRESS not set"); 87 | return; 88 | } 89 | wlr_log(WLR_INFO, "Updating dbus execution environment"); 90 | 91 | char *cmd = strdup_printf("dbus-update-activation-environment %s", env_keys); 92 | spawn_async_no_shell(cmd); 93 | free(cmd); 94 | 95 | cmd = strdup_printf("systemctl --user import-environment %s", env_keys); 96 | spawn_async_no_shell(cmd); 97 | free(cmd); 98 | } 99 | 100 | void 101 | session_environment_init(const char *dir) 102 | { 103 | /* 104 | * Set default for XDG_CURRENT_DESKTOP so xdg-desktop-portal-wlr is happy. 105 | * May be overriden either by already having a value set or by the user 106 | * supplied environment file. 107 | */ 108 | setenv("XDG_CURRENT_DESKTOP", "wlroots", 0); 109 | 110 | /* 111 | * Set default for _JAVA_AWT_WM_NONREPARENTING so that Java applications 112 | * such as JetBrains/Intellij Idea do render blank windows and menus 113 | * with incorrect offset. See https://github.com/swaywm/sway/issues/595 114 | * May be overriden either by already having a value set or by the user 115 | * supplied environment file. 116 | */ 117 | setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 0); 118 | 119 | char *environment = build_path(dir, "environment"); 120 | if (!environment) { 121 | return; 122 | } 123 | read_environment_file(environment); 124 | free(environment); 125 | } 126 | 127 | void 128 | session_autostart_init(const char *dir) 129 | { 130 | /* Update dbus and systemd user environment, each may fail gracefully */ 131 | update_activation_env("DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP"); 132 | 133 | char *autostart = build_path(dir, "autostart"); 134 | if (!autostart) { 135 | return; 136 | } 137 | if (!file_exists(autostart)) { 138 | wlr_log(WLR_ERROR, "no autostart file"); 139 | goto out; 140 | } 141 | wlr_log(WLR_INFO, "run autostart file %s", autostart); 142 | char *cmd = strdup_printf("sh %s", autostart); 143 | spawn_async_no_shell(cmd); 144 | free(cmd); 145 | out: 146 | free(autostart); 147 | } 148 | -------------------------------------------------------------------------------- /src/window-rules.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #define _POSIX_C_SOURCE 200809L 3 | #include "config.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "action.h" 11 | #include "common/match.h" 12 | #include "config/rcxml.h" 13 | #include "labwc.h" 14 | #include "view.h" 15 | #include "window-rules.h" 16 | 17 | static bool 18 | other_instances_exist(struct view *self, const char *id, const char *title) 19 | { 20 | struct wl_list *views = &self->server->views; 21 | const char *prop = NULL; 22 | struct view *view; 23 | 24 | wl_list_for_each(view, views, link) { 25 | if (view == self) { 26 | continue; 27 | } 28 | if (id) { 29 | prop = view_get_string_prop(view, "app_id"); 30 | if (prop && !strcmp(prop, id)) { 31 | return true; 32 | } 33 | } 34 | if (title) { 35 | prop = view_get_string_prop(view, "title"); 36 | if (prop && !strcmp(prop, title)) { 37 | return true; 38 | } 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | /* Try to match against identifier AND title (if set) */ 45 | static bool 46 | view_matches_criteria(struct window_rule *rule, struct view *view) 47 | { 48 | const char *id = view_get_string_prop(view, "app_id"); 49 | const char *title = view_get_string_prop(view, "title"); 50 | 51 | if (rule->match_once && other_instances_exist(view, id, title)) { 52 | return false; 53 | } 54 | 55 | if (rule->identifier && rule->title) { 56 | if (!id || !title) { 57 | return false; 58 | } 59 | return match_glob(rule->identifier, id) 60 | && match_glob(rule->title, title); 61 | } else if (rule->identifier) { 62 | if (!id) { 63 | return false; 64 | } 65 | return match_glob(rule->identifier, id); 66 | } else if (rule->title) { 67 | if (!title) { 68 | return false; 69 | } 70 | return match_glob(rule->title, title); 71 | } else { 72 | wlr_log(WLR_ERROR, "rule has no identifier or title\n"); 73 | return false; 74 | } 75 | } 76 | 77 | void 78 | window_rules_apply(struct view *view, enum window_rule_event event) 79 | { 80 | struct window_rule *rule; 81 | wl_list_for_each(rule, &rc.window_rules, link) { 82 | if (rule->event != event) { 83 | continue; 84 | } 85 | if (view_matches_criteria(rule, view)) { 86 | actions_run(view, view->server, &rule->actions, 0); 87 | } 88 | } 89 | } 90 | 91 | enum property 92 | window_rules_get_property(struct view *view, const char *property) 93 | { 94 | assert(property); 95 | 96 | /* 97 | * We iterate in reverse here because later items in list have higher 98 | * priority. For example, in the config below we want the return value 99 | * for foot's "serverDecoration" property to be "default". 100 | * 101 | * 102 | * 103 | * 104 | * 105 | */ 106 | struct window_rule *rule; 107 | wl_list_for_each_reverse(rule, &rc.window_rules, link) { 108 | /* 109 | * Only return if property != LAB_PROP_UNSPECIFIED otherwise a 110 | * which does not set a particular property 111 | * attribute would still return here if that property was asked 112 | * for. 113 | */ 114 | if (view_matches_criteria(rule, view)) { 115 | if (rule->server_decoration 116 | && !strcasecmp(property, "serverDecoration")) { 117 | return rule->server_decoration; 118 | } 119 | if (rule->skip_taskbar 120 | && !strcasecmp(property, "skipTaskbar")) { 121 | return rule->skip_taskbar; 122 | } 123 | if (rule->skip_window_switcher 124 | && !strcasecmp(property, "skipWindowSwitcher")) { 125 | return rule->skip_window_switcher; 126 | } 127 | if (rule->ignore_focus_request 128 | && !strcasecmp(property, "ignoreFocusRequest")) { 129 | return rule->ignore_focus_request; 130 | } 131 | if (rule->fixed_position 132 | && !strcasecmp(property, "fixedPosition")) { 133 | return rule->fixed_position; 134 | } 135 | } 136 | } 137 | return LAB_PROP_UNSPECIFIED; 138 | } 139 | -------------------------------------------------------------------------------- /include/input/cursor.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_CURSOR_H 3 | #define LABWC_CURSOR_H 4 | 5 | #include 6 | #include 7 | #include "ssd.h" 8 | 9 | struct view; 10 | struct seat; 11 | struct server; 12 | struct wlr_surface; 13 | struct wlr_scene_node; 14 | 15 | /* Cursors used internally by labwc */ 16 | enum lab_cursors { 17 | LAB_CURSOR_CLIENT = 0, 18 | LAB_CURSOR_DEFAULT, 19 | LAB_CURSOR_GRAB, 20 | LAB_CURSOR_RESIZE_NW, 21 | LAB_CURSOR_RESIZE_N, 22 | LAB_CURSOR_RESIZE_NE, 23 | LAB_CURSOR_RESIZE_E, 24 | LAB_CURSOR_RESIZE_SE, 25 | LAB_CURSOR_RESIZE_S, 26 | LAB_CURSOR_RESIZE_SW, 27 | LAB_CURSOR_RESIZE_W, 28 | LAB_CURSOR_COUNT 29 | }; 30 | 31 | struct cursor_context { 32 | struct view *view; 33 | struct wlr_scene_node *node; 34 | struct wlr_surface *surface; 35 | enum ssd_part_type type; 36 | double sx, sy; 37 | }; 38 | 39 | /** 40 | * get_cursor_context - find view and scene_node at cursor 41 | * 42 | * Behavior if node points to a surface: 43 | * - If surface is a layer-surface, type will be 44 | * set to LAB_SSD_LAYER_SURFACE and view will be NULL. 45 | * 46 | * - If surface is a 'lost' unmanaged xsurface (one 47 | * with a never-mapped parent view), type will 48 | * be set to LAB_SSD_UNMANAGED and view will be NULL. 49 | * 50 | * 'Lost' unmanaged xsurfaces are usually caused by 51 | * X11 applications opening popups without setting 52 | * the main window as parent. Example: VLC submenus. 53 | * 54 | * - Any other surface will cause type to be set to 55 | * LAB_SSD_CLIENT and return the attached view. 56 | * 57 | * Behavior if node points to internal elements: 58 | * - type will be set to the appropriate enum value 59 | * and view will be NULL if the node is not part of the SSD. 60 | * 61 | * If no node is found for the given layout coordinates, 62 | * type will be set to LAB_SSD_ROOT and view will be NULL. 63 | * 64 | */ 65 | struct cursor_context get_cursor_context(struct server *server); 66 | 67 | /** 68 | * cursor_set - set cursor icon 69 | * @seat - current seat 70 | * @cursor - name of cursor, for example LAB_CURSOR_DEFAULT or LAB_CURSOR_GRAB 71 | */ 72 | void cursor_set(struct seat *seat, enum lab_cursors cursor); 73 | 74 | /** 75 | * cursor_get_resize_edges - calculate resize edge based on cursor position 76 | * @cursor - the current cursor (usually server->seat.cursor) 77 | * @cursor_context - result of get_cursor_context() 78 | * 79 | * Calculates the resize edge combination that is most appropriate based 80 | * on the current view and cursor position in relation to each other. 81 | * 82 | * This is mostly important when either resizing a window using a 83 | * keyboard modifier or when using the Resize action from a keybind. 84 | */ 85 | uint32_t cursor_get_resize_edges(struct wlr_cursor *cursor, 86 | struct cursor_context *ctx); 87 | 88 | /** 89 | * cursor_get_from_edge - translate wlroots edge enum to lab_cursor enum 90 | * @resize_edges - WLR_EDGE_ combination like WLR_EDGE_TOP | WLR_EDGE_RIGHT 91 | * 92 | * Returns LAB_CURSOR_DEFAULT on WLR_EDGE_NONE 93 | * Returns the appropriate lab_cursors enum if @resize_edges 94 | * is one of the 4 corners or one of the 4 edges. 95 | * 96 | * Asserts on invalid edge combinations like WLR_EDGE_LEFT | WLR_EDGE_RIGHT 97 | */ 98 | enum lab_cursors cursor_get_from_edge(uint32_t resize_edges); 99 | 100 | /** 101 | * cursor_update_focus - update cursor focus, may update the cursor icon 102 | * @server - server 103 | * 104 | * This can be used to give the mouse focus to the surface under the cursor 105 | * or to force an update of the cursor icon by sending an exit and enter 106 | * event to an already focused surface. 107 | */ 108 | void cursor_update_focus(struct server *server); 109 | 110 | /** 111 | * cursor_update_image - re-set the labwc cursor image 112 | * @seat - seat 113 | * 114 | * This can be used to update the cursor image on output scale changes. 115 | * If the current cursor image was not set by labwc but some client 116 | * this is a no-op. 117 | */ 118 | void cursor_update_image(struct seat *seat); 119 | 120 | void cursor_init(struct seat *seat); 121 | void cursor_finish(struct seat *seat); 122 | 123 | #endif /* LABWC_CURSOR_H */ 124 | -------------------------------------------------------------------------------- /src/decorations/kde-deco.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include "common/list.h" 5 | #include "common/mem.h" 6 | #include "decorations.h" 7 | #include "labwc.h" 8 | #include "view.h" 9 | 10 | static struct wl_list decorations; 11 | static struct wlr_server_decoration_manager *kde_deco_mgr; 12 | 13 | struct kde_deco { 14 | struct wl_list link; /* decorations */ 15 | struct wlr_server_decoration *wlr_kde_decoration; 16 | struct view *view; 17 | struct wl_listener mode; 18 | struct wl_listener destroy; 19 | }; 20 | 21 | static void 22 | handle_destroy(struct wl_listener *listener, void *data) 23 | { 24 | struct kde_deco *kde_deco = wl_container_of(listener, kde_deco, destroy); 25 | wl_list_remove(&kde_deco->destroy.link); 26 | wl_list_remove(&kde_deco->mode.link); 27 | wl_list_remove(&kde_deco->link); 28 | free(kde_deco); 29 | } 30 | 31 | static void 32 | handle_mode(struct wl_listener *listener, void *data) 33 | { 34 | struct kde_deco *kde_deco = wl_container_of(listener, kde_deco, mode); 35 | if (!kde_deco->view) { 36 | return; 37 | } 38 | 39 | enum wlr_server_decoration_manager_mode client_mode = 40 | kde_deco->wlr_kde_decoration->mode; 41 | 42 | switch (client_mode) { 43 | case WLR_SERVER_DECORATION_MANAGER_MODE_SERVER: 44 | kde_deco->view->ssd_preference = LAB_SSD_PREF_SERVER; 45 | break; 46 | case WLR_SERVER_DECORATION_MANAGER_MODE_NONE: 47 | case WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT: 48 | kde_deco->view->ssd_preference = LAB_SSD_PREF_CLIENT; 49 | break; 50 | default: 51 | wlr_log(WLR_ERROR, "Unspecified kde decoration variant " 52 | "requested: %u", client_mode); 53 | } 54 | 55 | view_set_decorations(kde_deco->view, 56 | kde_deco->view->ssd_preference == LAB_SSD_PREF_SERVER); 57 | } 58 | 59 | static void 60 | handle_new_server_decoration(struct wl_listener *listener, void *data) 61 | { 62 | struct wlr_server_decoration *wlr_deco = data; 63 | struct kde_deco *kde_deco = znew(*kde_deco); 64 | kde_deco->wlr_kde_decoration = wlr_deco; 65 | 66 | if (wlr_deco->surface) { 67 | /* 68 | * Depending on the application event flow, the supplied 69 | * wlr_surface may already have been set up as a xdg_surface 70 | * or not (e.g. for GTK4). In the second case, the xdg.c 71 | * new_surface handler will try to set the view via 72 | * kde_server_decoration_set_view(). 73 | */ 74 | struct wlr_xdg_surface *xdg_surface = 75 | wlr_xdg_surface_try_from_wlr_surface(wlr_deco->surface); 76 | if (xdg_surface && xdg_surface->data) { 77 | kde_deco->view = (struct view *)xdg_surface->data; 78 | handle_mode(&kde_deco->mode, wlr_deco); 79 | } 80 | } 81 | 82 | wl_signal_add(&wlr_deco->events.destroy, &kde_deco->destroy); 83 | kde_deco->destroy.notify = handle_destroy; 84 | 85 | wl_signal_add(&wlr_deco->events.mode, &kde_deco->mode); 86 | kde_deco->mode.notify = handle_mode; 87 | 88 | wl_list_append(&decorations, &kde_deco->link); 89 | } 90 | 91 | void 92 | kde_server_decoration_set_view(struct view *view, struct wlr_surface *surface) 93 | { 94 | struct kde_deco *kde_deco; 95 | wl_list_for_each(kde_deco, &decorations, link) { 96 | if (kde_deco->wlr_kde_decoration->surface == surface) { 97 | if (!kde_deco->view) { 98 | kde_deco->view = view; 99 | handle_mode(&kde_deco->mode, kde_deco->wlr_kde_decoration); 100 | } 101 | return; 102 | } 103 | } 104 | } 105 | 106 | void 107 | kde_server_decoration_update_default(void) 108 | { 109 | assert(kde_deco_mgr); 110 | wlr_server_decoration_manager_set_default_mode(kde_deco_mgr, 111 | rc.xdg_shell_server_side_deco 112 | ? WLR_SERVER_DECORATION_MANAGER_MODE_SERVER 113 | : WLR_SERVER_DECORATION_MANAGER_MODE_CLIENT); 114 | } 115 | 116 | void 117 | kde_server_decoration_init(struct server *server) 118 | { 119 | assert(!kde_deco_mgr); 120 | kde_deco_mgr = wlr_server_decoration_manager_create(server->wl_display); 121 | if (!kde_deco_mgr) { 122 | wlr_log(WLR_ERROR, "unable to create the kde server deco manager"); 123 | exit(EXIT_FAILURE); 124 | } 125 | 126 | wl_list_init(&decorations); 127 | kde_server_decoration_update_default(); 128 | 129 | wl_signal_add(&kde_deco_mgr->events.new_decoration, &server->kde_server_decoration); 130 | server->kde_server_decoration.notify = handle_new_server_decoration; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /.github/workflows/irc.yml: -------------------------------------------------------------------------------- 1 | name: "IRC Notifications" 2 | on: 3 | create: 4 | pull_request: 5 | types: [opened, closed, reopened] 6 | issues: 7 | types: [opened, closed, reopened] 8 | push: 9 | branches: 10 | - 'master_disabled' 11 | - 'v0.5_disabled' 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: irc push 18 | uses: rectalogic/notify-irc@v1 19 | if: github.event_name == 'push' 20 | with: 21 | server: "irc.libera.chat" 22 | channel: "#labwc" 23 | nickname: "labwc" 24 | notice: true 25 | message: "[${{ github.event.ref }}] ${{ github.actor }} pushed new commits: ${{ github.event.compare }}" 26 | - name: irc issue opened 27 | uses: rectalogic/notify-irc@v1 28 | if: github.event_name == 'issues' && github.event.action == 'opened' 29 | with: 30 | server: "irc.libera.chat" 31 | channel: "#labwc" 32 | nickname: "labwc" 33 | notice: true 34 | message: "${{ github.actor }} opened issue '${{ github.event.issue.title }}' (${{ github.event.issue.html_url }})" 35 | - name: irc issue reopened 36 | uses: rectalogic/notify-irc@v1 37 | if: github.event_name == 'issues' && github.event.action == 'reopened' 38 | with: 39 | server: "irc.libera.chat" 40 | channel: "#labwc" 41 | nickname: "labwc" 42 | notice: true 43 | message: "${{ github.actor }} reopened issue: '${{ github.event.issue.title }}' (${{ github.event.issue.html_url }})" 44 | - name: irc issue closed 45 | uses: rectalogic/notify-irc@v1 46 | if: github.event_name == 'issues' && github.event.action == 'closed' 47 | with: 48 | server: "irc.libera.chat" 49 | channel: "#labwc" 50 | nickname: "labwc" 51 | notice: true 52 | message: "${{ github.actor }} closed issue '${{ github.event.issue.title }}' (${{ github.event.issue.html_url }})" 53 | - name: irc pull request opened 54 | uses: rectalogic/notify-irc@v1 55 | if: github.event_name == 'pull_request' && github.event.action == 'opened' 56 | with: 57 | server: "irc.libera.chat" 58 | channel: "#labwc" 59 | nickname: "labwc" 60 | notice: true 61 | message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} opened PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" 62 | - name: irc pull request reopened 63 | uses: rectalogic/notify-irc@v1 64 | if: github.event_name == 'pull_request' && github.event.action == 'reopened' 65 | with: 66 | server: "irc.libera.chat" 67 | channel: "#labwc" 68 | nickname: "labwc" 69 | notice: true 70 | message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} reopened PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" 71 | - name: irc pull request merged 72 | uses: rectalogic/notify-irc@v1 73 | if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true 74 | with: 75 | server: "irc.libera.chat" 76 | channel: "#labwc" 77 | nickname: "labwc" 78 | notice: true 79 | message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} merged PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" 80 | - name: irc pull request closed 81 | uses: rectalogic/notify-irc@v1 82 | if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == false 83 | with: 84 | server: "irc.libera.chat" 85 | channel: "#labwc" 86 | nickname: "labwc" 87 | notice: true 88 | message: "[${{ github.event.pull_request.base.ref }}] ${{ github.actor }} closed PR '${{ github.event.pull_request.title }}' (${{ github.event.pull_request.html_url }})" 89 | - name: irc tag created 90 | uses: rectalogic/notify-irc@v1 91 | if: github.event_name == 'create' && github.event.ref_type == 'tag' 92 | with: 93 | server: "irc.libera.chat" 94 | channel: "#labwc" 95 | nickname: "labwc" 96 | notice: true 97 | message: "${{ github.actor }} tagged ${{ github.repository }}: ${{ github.event.ref }}" 98 | -------------------------------------------------------------------------------- /include/theme.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | /* 3 | * Theme engine for labwc 4 | * 5 | * Copyright Johan Malm 2020-2021 6 | */ 7 | 8 | #ifndef LABWC_THEME_H 9 | #define LABWC_THEME_H 10 | 11 | #include 12 | #include 13 | 14 | enum lab_justification { 15 | LAB_JUSTIFY_LEFT, 16 | LAB_JUSTIFY_CENTER, 17 | LAB_JUSTIFY_RIGHT, 18 | }; 19 | 20 | struct theme { 21 | int border_width; 22 | int padding_height; 23 | int title_height; 24 | int menu_overlap_x; 25 | int menu_overlap_y; 26 | 27 | /* colors */ 28 | float window_active_border_color[4]; 29 | float window_inactive_border_color[4]; 30 | 31 | float window_toggled_keybinds_color[4]; 32 | 33 | float window_active_title_bg_color[4]; 34 | float window_inactive_title_bg_color[4]; 35 | 36 | float window_active_label_text_color[4]; 37 | float window_inactive_label_text_color[4]; 38 | enum lab_justification window_label_text_justify; 39 | 40 | /* button colors */ 41 | float window_active_button_menu_unpressed_image_color[4]; 42 | float window_active_button_iconify_unpressed_image_color[4]; 43 | float window_active_button_max_unpressed_image_color[4]; 44 | float window_active_button_close_unpressed_image_color[4]; 45 | float window_inactive_button_menu_unpressed_image_color[4]; 46 | float window_inactive_button_iconify_unpressed_image_color[4]; 47 | float window_inactive_button_max_unpressed_image_color[4]; 48 | float window_inactive_button_close_unpressed_image_color[4]; 49 | /* TODO: add pressed and hover colors for buttons */ 50 | 51 | int menu_item_padding_x; 52 | int menu_item_padding_y; 53 | 54 | float menu_items_bg_color[4]; 55 | float menu_items_text_color[4]; 56 | float menu_items_active_bg_color[4]; 57 | float menu_items_active_text_color[4]; 58 | 59 | int menu_min_width; 60 | int menu_max_width; 61 | 62 | int menu_separator_line_thickness; 63 | int menu_separator_padding_width; 64 | int menu_separator_padding_height; 65 | float menu_separator_color[4]; 66 | 67 | int osd_border_width; 68 | 69 | float osd_bg_color[4]; 70 | float osd_border_color[4]; 71 | float osd_label_text_color[4]; 72 | 73 | int osd_window_switcher_width; 74 | int osd_window_switcher_padding; 75 | int osd_window_switcher_item_padding_x; 76 | int osd_window_switcher_item_padding_y; 77 | int osd_window_switcher_item_active_border_width; 78 | 79 | int osd_workspace_switcher_boxes_width; 80 | int osd_workspace_switcher_boxes_height; 81 | 82 | /* textures */ 83 | struct lab_data_buffer *button_close_active_unpressed; 84 | struct lab_data_buffer *button_maximize_active_unpressed; 85 | struct lab_data_buffer *button_restore_active_unpressed; 86 | struct lab_data_buffer *button_iconify_active_unpressed; 87 | struct lab_data_buffer *button_menu_active_unpressed; 88 | 89 | struct lab_data_buffer *button_close_inactive_unpressed; 90 | struct lab_data_buffer *button_maximize_inactive_unpressed; 91 | struct lab_data_buffer *button_restore_inactive_unpressed; 92 | struct lab_data_buffer *button_iconify_inactive_unpressed; 93 | struct lab_data_buffer *button_menu_inactive_unpressed; 94 | 95 | struct lab_data_buffer *button_close_active_hover; 96 | struct lab_data_buffer *button_maximize_active_hover; 97 | struct lab_data_buffer *button_restore_active_hover; 98 | struct lab_data_buffer *button_iconify_active_hover; 99 | struct lab_data_buffer *button_menu_active_hover; 100 | 101 | struct lab_data_buffer *button_close_inactive_hover; 102 | struct lab_data_buffer *button_maximize_inactive_hover; 103 | struct lab_data_buffer *button_restore_inactive_hover; 104 | struct lab_data_buffer *button_iconify_inactive_hover; 105 | struct lab_data_buffer *button_menu_inactive_hover; 106 | 107 | struct lab_data_buffer *corner_top_left_active_normal; 108 | struct lab_data_buffer *corner_top_right_active_normal; 109 | struct lab_data_buffer *corner_top_left_inactive_normal; 110 | struct lab_data_buffer *corner_top_right_inactive_normal; 111 | 112 | /* not set in rc.xml/themerc, but derived from font & padding_height */ 113 | int osd_window_switcher_item_height; 114 | }; 115 | 116 | /** 117 | * theme_init - read openbox theme and generate button textures 118 | * @theme: theme data 119 | * @theme_name: theme-name in //openbox-3/themerc 120 | * Note is obtained in theme-dir.c 121 | */ 122 | void theme_init(struct theme *theme, const char *theme_name); 123 | 124 | /** 125 | * theme_finish - free button textures 126 | * @theme: theme data 127 | */ 128 | void theme_finish(struct theme *theme); 129 | 130 | #endif /* LABWC_THEME_H */ 131 | -------------------------------------------------------------------------------- /src/foreign.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include "labwc.h" 4 | #include "view.h" 5 | #include "workspaces.h" 6 | 7 | static void 8 | handle_request_minimize(struct wl_listener *listener, void *data) 9 | { 10 | struct view *view = wl_container_of(listener, view, toplevel.minimize); 11 | struct wlr_foreign_toplevel_handle_v1_minimized_event *event = data; 12 | view_minimize(view, event->minimized); 13 | } 14 | 15 | static void 16 | handle_request_maximize(struct wl_listener *listener, void *data) 17 | { 18 | struct view *view = wl_container_of(listener, view, toplevel.maximize); 19 | struct wlr_foreign_toplevel_handle_v1_maximized_event *event = data; 20 | view_maximize(view, event->maximized ? VIEW_AXIS_BOTH : VIEW_AXIS_NONE, 21 | /*store_natural_geometry*/ true); 22 | } 23 | 24 | static void 25 | handle_request_fullscreen(struct wl_listener *listener, void *data) 26 | { 27 | struct view *view = wl_container_of(listener, view, toplevel.fullscreen); 28 | struct wlr_foreign_toplevel_handle_v1_fullscreen_event *event = data; 29 | view_set_fullscreen(view, event->fullscreen); 30 | } 31 | 32 | static void 33 | handle_request_activate(struct wl_listener *listener, void *data) 34 | { 35 | struct view *view = wl_container_of(listener, view, toplevel.activate); 36 | // struct wlr_foreign_toplevel_handle_v1_activated_event *event = data; 37 | /* In a multi-seat world we would select seat based on event->seat here. */ 38 | desktop_focus_view(view, /*raise*/ true); 39 | } 40 | 41 | static void 42 | handle_request_close(struct wl_listener *listener, void *data) 43 | { 44 | struct view *view = wl_container_of(listener, view, toplevel.close); 45 | view_close(view); 46 | } 47 | 48 | static void 49 | handle_destroy(struct wl_listener *listener, void *data) 50 | { 51 | struct view *view = wl_container_of(listener, view, toplevel.destroy); 52 | struct foreign_toplevel *toplevel = &view->toplevel; 53 | wl_list_remove(&toplevel->maximize.link); 54 | wl_list_remove(&toplevel->minimize.link); 55 | wl_list_remove(&toplevel->fullscreen.link); 56 | wl_list_remove(&toplevel->activate.link); 57 | wl_list_remove(&toplevel->close.link); 58 | wl_list_remove(&toplevel->destroy.link); 59 | toplevel->handle = NULL; 60 | } 61 | 62 | void 63 | foreign_toplevel_handle_create(struct view *view) 64 | { 65 | struct foreign_toplevel *toplevel = &view->toplevel; 66 | 67 | toplevel->handle = wlr_foreign_toplevel_handle_v1_create( 68 | view->server->foreign_toplevel_manager); 69 | if (!toplevel->handle) { 70 | wlr_log(WLR_ERROR, "cannot create foreign toplevel handle for (%s)", 71 | view_get_string_prop(view, "title")); 72 | return; 73 | } 74 | 75 | toplevel->maximize.notify = handle_request_maximize; 76 | wl_signal_add(&toplevel->handle->events.request_maximize, 77 | &toplevel->maximize); 78 | 79 | toplevel->minimize.notify = handle_request_minimize; 80 | wl_signal_add(&toplevel->handle->events.request_minimize, 81 | &toplevel->minimize); 82 | 83 | toplevel->fullscreen.notify = handle_request_fullscreen; 84 | wl_signal_add(&toplevel->handle->events.request_fullscreen, 85 | &toplevel->fullscreen); 86 | 87 | toplevel->activate.notify = handle_request_activate; 88 | wl_signal_add(&toplevel->handle->events.request_activate, 89 | &toplevel->activate); 90 | 91 | toplevel->close.notify = handle_request_close; 92 | wl_signal_add(&toplevel->handle->events.request_close, 93 | &toplevel->close); 94 | 95 | toplevel->destroy.notify = handle_destroy; 96 | wl_signal_add(&toplevel->handle->events.destroy, &toplevel->destroy); 97 | } 98 | 99 | /* 100 | * Loop over all outputs and notify foreign_toplevel clients about changes. 101 | * wlr_foreign_toplevel_handle_v1_output_xxx() keeps track of the active 102 | * outputs internally and merges the events. It also listens to output 103 | * destroy events so its fine to just relay the current state and let 104 | * wlr_foreign_toplevel handle the rest. 105 | */ 106 | void 107 | foreign_toplevel_update_outputs(struct view *view) 108 | { 109 | assert(view->toplevel.handle); 110 | struct wlr_output_layout *layout = view->server->output_layout; 111 | struct output *output; 112 | wl_list_for_each(output, &view->server->outputs, link) { 113 | if (output_is_usable(output) && wlr_output_layout_intersects( 114 | layout, output->wlr_output, &view->current)) { 115 | wlr_foreign_toplevel_handle_v1_output_enter( 116 | view->toplevel.handle, output->wlr_output); 117 | } else { 118 | wlr_foreign_toplevel_handle_v1_output_leave( 119 | view->toplevel.handle, output->wlr_output); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/common/font.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "common/font.h" 10 | #include "common/graphic-helpers.h" 11 | #include "labwc.h" 12 | #include "buffer.h" 13 | 14 | PangoFontDescription * 15 | font_to_pango_desc(struct font *font) 16 | { 17 | PangoFontDescription *desc = pango_font_description_new(); 18 | pango_font_description_set_family(desc, font->name); 19 | pango_font_description_set_size(desc, font->size * PANGO_SCALE); 20 | if (font->slant == FONT_SLANT_ITALIC) { 21 | pango_font_description_set_style(desc, PANGO_STYLE_ITALIC); 22 | } 23 | if (font->weight == FONT_WEIGHT_BOLD) { 24 | pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); 25 | } 26 | return desc; 27 | } 28 | 29 | static PangoRectangle 30 | font_extents(struct font *font, const char *string) 31 | { 32 | PangoRectangle rect = { 0 }; 33 | if (!string) { 34 | return rect; 35 | } 36 | cairo_surface_t *surface; 37 | cairo_t *c; 38 | PangoLayout *layout; 39 | 40 | surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); 41 | c = cairo_create(surface); 42 | layout = pango_cairo_create_layout(c); 43 | PangoFontDescription *desc = font_to_pango_desc(font); 44 | 45 | pango_layout_set_font_description(layout, desc); 46 | pango_layout_set_text(layout, string, -1); 47 | pango_layout_set_single_paragraph_mode(layout, TRUE); 48 | pango_layout_set_width(layout, -1); 49 | pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_MIDDLE); 50 | pango_layout_get_extents(layout, NULL, &rect); 51 | pango_extents_to_pixels(&rect, NULL); 52 | 53 | /* we put a 2 px edge on each side - because Openbox does it :) */ 54 | /* TODO: remove the 4 pixel addition and always do the padding by the caller */ 55 | rect.width += 4; 56 | 57 | cairo_destroy(c); 58 | cairo_surface_destroy(surface); 59 | pango_font_description_free(desc); 60 | g_object_unref(layout); 61 | return rect; 62 | } 63 | 64 | int 65 | font_height(struct font *font) 66 | { 67 | PangoRectangle rectangle = font_extents(font, "abcdefg"); 68 | return rectangle.height; 69 | } 70 | 71 | int 72 | font_width(struct font *font, const char *string) 73 | { 74 | PangoRectangle rectangle = font_extents(font, string); 75 | return rectangle.width; 76 | } 77 | 78 | void 79 | font_buffer_create(struct lab_data_buffer **buffer, int max_width, 80 | const char *text, struct font *font, float *color, const char *arrow, 81 | double scale) 82 | { 83 | /* Allow a minimum of one pixel each for text and arrow */ 84 | if (max_width < 2) { 85 | max_width = 2; 86 | } 87 | 88 | if (!text || !*text) { 89 | return; 90 | } 91 | 92 | PangoRectangle text_extents = font_extents(font, text); 93 | PangoRectangle arrow_extents = font_extents(font, arrow); 94 | 95 | if (arrow) { 96 | if (arrow_extents.width >= max_width - 1) { 97 | /* It would be weird to get here, but just in case */ 98 | arrow_extents.width = max_width - 1; 99 | text_extents.width = 1; 100 | } else { 101 | text_extents.width = max_width - arrow_extents.width; 102 | } 103 | } else if (text_extents.width > max_width) { 104 | text_extents.width = max_width; 105 | } 106 | 107 | *buffer = buffer_create_cairo(text_extents.width + arrow_extents.width, 108 | text_extents.height, scale, true); 109 | if (!*buffer) { 110 | wlr_log(WLR_ERROR, "Failed to create font buffer"); 111 | return; 112 | } 113 | 114 | cairo_t *cairo = (*buffer)->cairo; 115 | cairo_surface_t *surf = cairo_get_target(cairo); 116 | 117 | set_cairo_color(cairo, color); 118 | cairo_move_to(cairo, 0, 0); 119 | 120 | PangoLayout *layout = pango_cairo_create_layout(cairo); 121 | pango_layout_set_width(layout, text_extents.width * PANGO_SCALE); 122 | pango_layout_set_text(layout, text, -1); 123 | pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); 124 | 125 | PangoFontDescription *desc = font_to_pango_desc(font); 126 | pango_layout_set_font_description(layout, desc); 127 | pango_font_description_free(desc); 128 | pango_cairo_update_layout(cairo, layout); 129 | pango_cairo_show_layout(cairo, layout); 130 | 131 | if (arrow) { 132 | cairo_move_to(cairo, text_extents.width, 0); 133 | pango_layout_set_width(layout, arrow_extents.width * PANGO_SCALE); 134 | pango_layout_set_text(layout, arrow, -1); 135 | pango_cairo_show_layout(cairo, layout); 136 | } 137 | 138 | g_object_unref(layout); 139 | 140 | cairo_surface_flush(surf); 141 | } 142 | 143 | void 144 | font_finish(void) 145 | { 146 | pango_cairo_font_map_set_default(NULL); 147 | } 148 | -------------------------------------------------------------------------------- /include/ssd-internal.h: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: GPL-2.0-only */ 2 | #ifndef LABWC_SSD_INTERNAL_H 3 | #define LABWC_SSD_INTERNAL_H 4 | 5 | #include 6 | #include "common/macros.h" 7 | #include "ssd.h" 8 | #include "view.h" 9 | 10 | #define FOR_EACH(tmp, ...) \ 11 | { \ 12 | __typeof__(tmp) _x[] = { __VA_ARGS__, NULL }; \ 13 | size_t _i = 0; \ 14 | for ((tmp) = _x[_i]; _i < ARRAY_SIZE(_x) - 1; (tmp) = _x[++_i]) 15 | 16 | #define FOR_EACH_END } 17 | 18 | struct ssd_button { 19 | struct view *view; 20 | enum ssd_part_type type; 21 | struct wlr_scene_node *normal; 22 | struct wlr_scene_node *hover; 23 | struct wlr_scene_node *toggled; 24 | struct wlr_scene_node *toggled_hover; 25 | struct wlr_scene_node *background; 26 | struct wlr_scene_tree *icon_tree; 27 | struct wlr_scene_tree *hover_tree; 28 | 29 | struct wl_listener destroy; 30 | }; 31 | 32 | struct ssd_sub_tree { 33 | struct wlr_scene_tree *tree; 34 | struct wl_list parts; /* ssd_part.link */ 35 | }; 36 | 37 | struct ssd_state_title_width { 38 | int width; 39 | bool truncated; 40 | }; 41 | 42 | struct ssd { 43 | struct view *view; 44 | struct wlr_scene_tree *tree; 45 | 46 | /* 47 | * Cache for current values. 48 | * Used to detect actual changes so we 49 | * don't update things we don't have to. 50 | */ 51 | struct { 52 | bool was_maximized; /* To un-round corner buttons and toggle icon on maximize */ 53 | struct wlr_box geometry; 54 | struct ssd_state_title { 55 | char *text; 56 | struct ssd_state_title_width active; 57 | struct ssd_state_title_width inactive; 58 | } title; 59 | } state; 60 | 61 | /* An invisible area around the view which allows resizing */ 62 | struct ssd_sub_tree extents; 63 | 64 | /* The top of the view, containing buttons, title, .. */ 65 | struct { 66 | int height; 67 | struct wlr_scene_tree *tree; 68 | struct ssd_sub_tree active; 69 | struct ssd_sub_tree inactive; 70 | } titlebar; 71 | 72 | /* Borders allow resizing as well */ 73 | struct { 74 | struct wlr_scene_tree *tree; 75 | struct ssd_sub_tree active; 76 | struct ssd_sub_tree inactive; 77 | } border; 78 | 79 | /* 80 | * Space between the extremities of the view's wlr_surface 81 | * and the max extents of the server-side decorations. 82 | * For xdg-shell views with CSD, this margin is zero. 83 | */ 84 | struct border margin; 85 | }; 86 | 87 | struct ssd_part { 88 | enum ssd_part_type type; 89 | 90 | /* Buffer pointer. May be NULL */ 91 | struct scaled_font_buffer *buffer; 92 | 93 | /* This part represented in scene graph */ 94 | struct wlr_scene_node *node; 95 | 96 | /* Targeted geometry. May be NULL */ 97 | struct wlr_box *geometry; 98 | 99 | struct wl_list link; 100 | }; 101 | 102 | struct ssd_hover_state { 103 | struct view *view; 104 | struct ssd_button *button; 105 | }; 106 | 107 | struct wlr_buffer; 108 | struct wlr_scene_tree; 109 | 110 | /* SSD internal helpers to create various SSD elements */ 111 | /* TODO: Replace some common args with a struct */ 112 | struct ssd_part *add_scene_part( 113 | struct wl_list *part_list, enum ssd_part_type type); 114 | struct ssd_part *add_scene_rect( 115 | struct wl_list *list, enum ssd_part_type type, 116 | struct wlr_scene_tree *parent, int width, int height, int x, int y, 117 | float color[4]); 118 | struct ssd_part *add_scene_buffer( 119 | struct wl_list *list, enum ssd_part_type type, 120 | struct wlr_scene_tree *parent, struct wlr_buffer *buffer, int x, int y); 121 | struct ssd_part *add_scene_button( 122 | struct wl_list *part_list, enum ssd_part_type type, 123 | struct wlr_scene_tree *parent, float *bg_color, 124 | struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer, 125 | int x, struct view *view); 126 | void 127 | add_toggled_icon(struct wl_list *part_list, enum ssd_part_type type, 128 | struct wlr_buffer *icon_buffer, struct wlr_buffer *hover_buffer); 129 | struct ssd_part *add_scene_button_corner( 130 | struct wl_list *part_list, enum ssd_part_type type, 131 | enum ssd_part_type corner_type, struct wlr_scene_tree *parent, 132 | struct wlr_buffer *corner_buffer, struct wlr_buffer *icon_buffer, 133 | struct wlr_buffer *hover_buffer, int x, struct view *view); 134 | 135 | /* SSD internal helpers */ 136 | struct ssd_part *ssd_get_part( 137 | struct wl_list *part_list, enum ssd_part_type type); 138 | void ssd_destroy_parts(struct wl_list *list); 139 | 140 | /* SSD internal */ 141 | void ssd_titlebar_create(struct ssd *ssd); 142 | void ssd_titlebar_update(struct ssd *ssd); 143 | void ssd_titlebar_destroy(struct ssd *ssd); 144 | 145 | void ssd_border_create(struct ssd *ssd); 146 | void ssd_border_update(struct ssd *ssd); 147 | void ssd_border_destroy(struct ssd *ssd); 148 | 149 | void ssd_extents_create(struct ssd *ssd); 150 | void ssd_extents_update(struct ssd *ssd); 151 | void ssd_extents_destroy(struct ssd *ssd); 152 | 153 | #endif /* LABWC_SSD_INTERNAL_H */ 154 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | /* 3 | * Based on wlroots/types/wlr_buffer.c 4 | * 5 | * Copyright (c) 2017, 2018 Drew DeVault 6 | * Copyright (c) 2018-2021 Simon Ser, Simon Zeni 7 | 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "buffer.h" 32 | #include "common/mem.h" 33 | 34 | static const struct wlr_buffer_impl data_buffer_impl; 35 | 36 | static struct lab_data_buffer * 37 | data_buffer_from_buffer(struct wlr_buffer *buffer) 38 | { 39 | assert(buffer->impl == &data_buffer_impl); 40 | return (struct lab_data_buffer *)buffer; 41 | } 42 | 43 | static void 44 | data_buffer_destroy(struct wlr_buffer *wlr_buffer) 45 | { 46 | struct lab_data_buffer *buffer = data_buffer_from_buffer(wlr_buffer); 47 | if (!buffer->free_on_destroy) { 48 | free(buffer); 49 | return; 50 | } 51 | if (buffer->cairo) { 52 | cairo_surface_t *surf = cairo_get_target(buffer->cairo); 53 | cairo_destroy(buffer->cairo); 54 | cairo_surface_destroy(surf); 55 | } else if (buffer->data) { 56 | free(buffer->data); 57 | buffer->data = NULL; 58 | } 59 | free(buffer); 60 | } 61 | 62 | static bool 63 | data_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, uint32_t flags, 64 | void **data, uint32_t *format, size_t *stride) 65 | { 66 | struct lab_data_buffer *buffer = 67 | wl_container_of(wlr_buffer, buffer, base); 68 | assert(buffer->data); 69 | *data = (void *)buffer->data; 70 | *format = buffer->format; 71 | *stride = buffer->stride; 72 | return true; 73 | } 74 | 75 | static void 76 | data_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) 77 | { 78 | /* noop */ 79 | } 80 | 81 | static const struct wlr_buffer_impl data_buffer_impl = { 82 | .destroy = data_buffer_destroy, 83 | .begin_data_ptr_access = data_buffer_begin_data_ptr_access, 84 | .end_data_ptr_access = data_buffer_end_data_ptr_access, 85 | }; 86 | 87 | struct lab_data_buffer * 88 | buffer_create_cairo(uint32_t width, uint32_t height, float scale, 89 | bool free_on_destroy) 90 | { 91 | struct lab_data_buffer *buffer = znew(*buffer); 92 | buffer->unscaled_width = width; 93 | buffer->unscaled_height = height; 94 | width *= scale; 95 | height *= scale; 96 | 97 | /* Allocate the buffer with the scaled size */ 98 | wlr_buffer_init(&buffer->base, &data_buffer_impl, width, height); 99 | cairo_surface_t *surf = 100 | cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); 101 | 102 | /** 103 | * Tell cairo about the device scale so we can keep drawing in unscaled 104 | * coordinate space. Pango will automatically use the cairo scale attribute 105 | * as well when creating text on this surface. 106 | * 107 | * For a more complete explanation see PR #389 108 | */ 109 | cairo_surface_set_device_scale(surf, scale, scale); 110 | 111 | buffer->cairo = cairo_create(surf); 112 | buffer->data = cairo_image_surface_get_data(surf); 113 | buffer->format = DRM_FORMAT_ARGB8888; 114 | buffer->stride = cairo_image_surface_get_stride(surf); 115 | buffer->free_on_destroy = free_on_destroy; 116 | 117 | if (!buffer->data) { 118 | cairo_destroy(buffer->cairo); 119 | cairo_surface_destroy(surf); 120 | free(buffer); 121 | buffer = NULL; 122 | } 123 | return buffer; 124 | } 125 | 126 | struct lab_data_buffer * 127 | buffer_create_wrap(void *pixel_data, uint32_t width, uint32_t height, 128 | uint32_t stride, bool free_on_destroy) 129 | { 130 | struct lab_data_buffer *buffer = znew(*buffer); 131 | wlr_buffer_init(&buffer->base, &data_buffer_impl, width, height); 132 | buffer->data = pixel_data; 133 | buffer->format = DRM_FORMAT_ARGB8888; 134 | buffer->stride = stride; 135 | buffer->free_on_destroy = free_on_destroy; 136 | return buffer; 137 | } 138 | -------------------------------------------------------------------------------- /src/ssd/ssd_border.c: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-2.0-only 2 | 3 | #include 4 | #include "common/scene-helpers.h" 5 | #include "labwc.h" 6 | #include "ssd-internal.h" 7 | #include "theme.h" 8 | #include "view.h" 9 | 10 | #define FOR_EACH_STATE(ssd, tmp) FOR_EACH(tmp, \ 11 | &(ssd)->border.active, \ 12 | &(ssd)->border.inactive) 13 | 14 | void 15 | ssd_border_create(struct ssd *ssd) 16 | { 17 | assert(ssd); 18 | assert(!ssd->border.tree); 19 | 20 | struct view *view = ssd->view; 21 | struct theme *theme = view->server->theme; 22 | int width = view->current.width; 23 | int height = view->current.height; 24 | int full_width = width + 2 * theme->border_width; 25 | 26 | float *color; 27 | struct wlr_scene_tree *parent; 28 | struct ssd_sub_tree *subtree; 29 | 30 | ssd->border.tree = wlr_scene_tree_create(ssd->tree); 31 | wlr_scene_node_set_position(&ssd->border.tree->node, -theme->border_width, 0); 32 | 33 | FOR_EACH_STATE(ssd, subtree) { 34 | subtree->tree = wlr_scene_tree_create(ssd->border.tree); 35 | parent = subtree->tree; 36 | if (subtree == &ssd->border.active) { 37 | color = theme->window_active_border_color; 38 | } else { 39 | color = theme->window_inactive_border_color; 40 | wlr_scene_node_set_enabled(&parent->node, false); 41 | } 42 | wl_list_init(&subtree->parts); 43 | add_scene_rect(&subtree->parts, LAB_SSD_PART_LEFT, parent, 44 | theme->border_width, height, 0, 0, color); 45 | add_scene_rect(&subtree->parts, LAB_SSD_PART_RIGHT, parent, 46 | theme->border_width, height, 47 | theme->border_width + width, 0, color); 48 | add_scene_rect(&subtree->parts, LAB_SSD_PART_BOTTOM, parent, 49 | full_width, theme->border_width, 0, height, color); 50 | add_scene_rect(&subtree->parts, LAB_SSD_PART_TOP, parent, 51 | width - 2 * SSD_BUTTON_WIDTH, theme->border_width, 52 | theme->border_width + SSD_BUTTON_WIDTH, 53 | -(ssd->titlebar.height + theme->border_width), color); 54 | } FOR_EACH_END 55 | 56 | if (view->maximized == VIEW_AXIS_BOTH) { 57 | wlr_scene_node_set_enabled(&ssd->border.tree->node, false); 58 | } 59 | } 60 | 61 | void 62 | ssd_border_update(struct ssd *ssd) 63 | { 64 | assert(ssd); 65 | assert(ssd->border.tree); 66 | 67 | struct view *view = ssd->view; 68 | if (view->maximized == VIEW_AXIS_BOTH 69 | && ssd->border.tree->node.enabled) { 70 | /* Disable borders on maximize */ 71 | wlr_scene_node_set_enabled(&ssd->border.tree->node, false); 72 | ssd->margin = ssd_thickness(ssd->view); 73 | } 74 | 75 | if (view->maximized == VIEW_AXIS_BOTH) { 76 | return; 77 | } else if (!ssd->border.tree->node.enabled) { 78 | /* And re-enabled them when unmaximized */ 79 | wlr_scene_node_set_enabled(&ssd->border.tree->node, true); 80 | ssd->margin = ssd_thickness(ssd->view); 81 | } 82 | 83 | struct theme *theme = view->server->theme; 84 | 85 | int width = view->current.width; 86 | int height = view->current.height; 87 | int full_width = width + 2 * theme->border_width; 88 | 89 | struct ssd_part *part; 90 | struct wlr_scene_rect *rect; 91 | struct ssd_sub_tree *subtree; 92 | FOR_EACH_STATE(ssd, subtree) { 93 | wl_list_for_each(part, &subtree->parts, link) { 94 | rect = wlr_scene_rect_from_node(part->node); 95 | switch (part->type) { 96 | case LAB_SSD_PART_LEFT: 97 | wlr_scene_rect_set_size(rect, 98 | theme->border_width, height); 99 | continue; 100 | case LAB_SSD_PART_RIGHT: 101 | wlr_scene_rect_set_size(rect, 102 | theme->border_width, height); 103 | wlr_scene_node_set_position(part->node, 104 | theme->border_width + width, 0); 105 | continue; 106 | case LAB_SSD_PART_BOTTOM: 107 | wlr_scene_rect_set_size(rect, 108 | full_width, theme->border_width); 109 | wlr_scene_node_set_position(part->node, 110 | 0, height); 111 | continue; 112 | case LAB_SSD_PART_TOP: 113 | if (ssd->titlebar.height > 0) { 114 | wlr_scene_rect_set_size(rect, 115 | width - 2 * SSD_BUTTON_WIDTH, 116 | theme->border_width); 117 | wlr_scene_node_set_position(part->node, 118 | theme->border_width + SSD_BUTTON_WIDTH, 119 | -(ssd->titlebar.height + theme->border_width)); 120 | } else { 121 | wlr_scene_rect_set_size(rect, 122 | full_width, theme->border_width); 123 | wlr_scene_node_set_position(part->node, 124 | 0, -theme->border_width); 125 | } 126 | continue; 127 | default: 128 | continue; 129 | } 130 | } 131 | } FOR_EACH_END 132 | } 133 | 134 | void 135 | ssd_border_destroy(struct ssd *ssd) 136 | { 137 | assert(ssd); 138 | assert(ssd->border.tree); 139 | 140 | struct ssd_sub_tree *subtree; 141 | FOR_EACH_STATE(ssd, subtree) { 142 | ssd_destroy_parts(&subtree->parts); 143 | wlr_scene_node_destroy(&subtree->tree->node); 144 | subtree->tree = NULL; 145 | } FOR_EACH_END 146 | 147 | wlr_scene_node_destroy(&ssd->border.tree->node); 148 | ssd->border.tree = NULL; 149 | } 150 | 151 | #undef FOR_EACH_STATE 152 | --------------------------------------------------------------------------------