├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── clean_man_files.sh ├── config ├── config.def.h └── key_chords.def.h ├── examples ├── basic_example.wks ├── chord_array_example.wks ├── chord_expressions.wks ├── delimiter_examples.wks ├── error_example.wks ├── extended_example.wks ├── include_examples │ ├── browser_key_chords.wks │ ├── emacs_key_chords.wks │ ├── main.wks │ └── mpc_key_chords.wks ├── logo_example.wks ├── long_text_example.wks ├── preprocessor_example.wks ├── self.wks ├── sorted_example.wks ├── sorted_with_ignore_sort_example.wks ├── special_keys_example.wks ├── truncation_test.wks ├── unicode_example.wks └── upper_and_lower_example.wks ├── man ├── wk.1.man ├── wk.1.org ├── wks.5.man └── wks.5.org ├── src ├── common │ ├── common.c │ ├── common.h │ ├── debug.c │ ├── debug.h │ ├── key_chord.c │ ├── key_chord.h │ ├── memory.c │ ├── memory.h │ ├── menu.c │ ├── menu.h │ ├── string.c │ └── string.h ├── compiler │ ├── common.c │ ├── common.h │ ├── compiler.c │ ├── compiler.h │ ├── debug.c │ ├── debug.h │ ├── piece_table.c │ ├── piece_table.h │ ├── preprocessor.c │ ├── preprocessor.h │ ├── scanner.c │ ├── scanner.h │ ├── token.c │ ├── token.h │ ├── writer.c │ └── writer.h ├── main.c └── runtime │ ├── cairo.c │ ├── cairo.h │ ├── common.c │ ├── common.h │ ├── debug.c │ ├── debug.h │ ├── wayland │ ├── debug.c │ ├── debug.h │ ├── registry.c │ ├── registry.h │ ├── wayland.c │ ├── wayland.h │ ├── window.c │ ├── window.h │ └── wlr-layer-shell-unstable-v1.xml │ └── x11 │ ├── debug.c │ ├── debug.h │ ├── window.c │ └── window.h └── wk-which-key.png /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | src/runtime/wayland/wlr-layer-shell-unstable-v1.* 3 | src/runtime/wayland/xdg-shell.* 4 | config/config.h 5 | config/key_chords.h 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Package info 2 | NAME := wk 3 | VERSION := 0.1.3 4 | 5 | # Tools 6 | PKG_CONFIG ?= pkg-config 7 | 8 | # Install locations 9 | PREFIX := /usr/local 10 | MANPREFIX := $(PREFIX)/share/man 11 | 12 | # Project directories 13 | BUILD_DIR := ./build 14 | CONF_DIR := ./config 15 | MAN_DIR := ./man 16 | SOURCE_DIR := ./src 17 | COMMON_DIR := $(SOURCE_DIR)/common 18 | RUNTIME_DIR := $(SOURCE_DIR)/runtime 19 | COMPILER_DIR := $(SOURCE_DIR)/compiler 20 | X11_DIR := $(RUNTIME_DIR)/x11 21 | WAY_DIR := $(RUNTIME_DIR)/wayland 22 | 23 | # Files 24 | HEADERS := $(wildcard $(SOURCE_DIR)/*.h) $(CONF_DIR)/config.h $(CONF_DIR)/key_chords.h 25 | SOURCES := $(wildcard $(SOURCE_DIR)/*.c) 26 | OBJECTS := $(addprefix $(BUILD_DIR)/, $(notdir $(SOURCES:.c=.o))) 27 | COMM_OBJS := $(patsubst $(COMMON_DIR)/%.c, $(BUILD_DIR)/common/%.o, \ 28 | $(wildcard $(COMMON_DIR)/*.c)) 29 | COMP_OBJS := $(patsubst $(COMPILER_DIR)/%.c, $(BUILD_DIR)/compiler/%.o, \ 30 | $(wildcard $(COMPILER_DIR)/*.c)) 31 | RUN_OBJS := $(patsubst $(RUNTIME_DIR)/%.c, $(BUILD_DIR)/runtime/%.o, \ 32 | $(wildcard $(RUNTIME_DIR)/*.c)) 33 | X11_OBJS := $(patsubst $(X11_DIR)/%.c, $(BUILD_DIR)/runtime/x11/%.o, \ 34 | $(wildcard $(X11_DIR)/*.c)) 35 | WAY_SRCS := $(WAY_DIR)/xdg-shell.c $(WAY_DIR)/wlr-layer-shell-unstable-v1.c 36 | WAY_HDRS := $(WAY_DIR)/wlr-layer-shell-unstable-v1.h 37 | WAY_FILES := $(WAY_SRCS) $(WAY_HDRS) 38 | WAY_OBJS := $(patsubst $(WAY_DIR)/%.c, $(BUILD_DIR)/runtime/wayland/%.o, \ 39 | $(wildcard $(WAY_DIR)/*.c) $(WAY_SRCS)) 40 | MAN_FILES := $(wildcard $(MAN_DIR)/*.man) 41 | 42 | # Flags 43 | CFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -DVERSION=\"$(VERSION)\" -MMD -MP \ 44 | -iquote. -iquote$(SOURCE_DIR) 45 | CFLAGS += $(shell $(PKG_CONFIG) --cflags cairo pango pangocairo) 46 | LDFLAGS += $(shell $(PKG_CONFIG) --libs cairo pango pangocairo) 47 | X11_CFLAGS += -DWK_X11_BACKEND $(shell $(PKG_CONFIG) --cflags x11 xinerama) 48 | X11_LDFLAGS += $(shell $(PKG_CONFIG) --libs x11 xinerama) 49 | WAY_CFLAGS += -DWK_WAYLAND_BACKEND $(shell $(PKG_CONFIG) --cflags wayland-client xkbcommon) 50 | WAY_LDFLAGS += $(shell $(PKG_CONFIG) --libs wayland-client xkbcommon) 51 | 52 | # Make goals 53 | ALL_GOALS := all debug 54 | X11_GOALS := x11 debug-x11 55 | WAY_GOALS := wayland debug-wayland 56 | 57 | # Insert implicit 'all' target 58 | ifeq (0,$(words $(MAKECMDGOALS))) 59 | MAKECMDGOALS := all 60 | endif 61 | 62 | # Include relevant objects and flags for ALL_GOALS 63 | ifneq (0,$(words $(filter $(ALL_GOALS),$(MAKECMDGOALS)))) 64 | TARGET_OBJS := $(X11_OBJS) $(WAY_OBJS) 65 | CFLAGS += $(X11_CFLAGS) $(WAY_CFLAGS) 66 | LDFLAGS += $(X11_LDFLAGS) $(WAY_LDFLAGS) 67 | endif 68 | 69 | # Include relevant objects and flags for X11_GOALS 70 | ifneq (0,$(words $(filter $(X11_GOALS),$(MAKECMDGOALS)))) 71 | TARGET_OBJS := $(X11_OBJS) 72 | CFLAGS += $(X11_CFLAGS) 73 | LDFLAGS += $(X11_LDFLAGS) 74 | endif 75 | 76 | # Include relevant objects and flags for WAY_GOALS 77 | ifneq (0,$(words $(filter $(WAY_GOALS),$(MAKECMDGOALS)))) 78 | TARGET_OBJS := $(WAY_OBJS) 79 | CFLAGS += $(WAY_CFLAGS) 80 | LDFLAGS += $(WAY_LDFLAGS) 81 | endif 82 | 83 | # Targets 84 | all: options 85 | all: $(WAY_FILES) 86 | all: $(BUILD_DIR)/$(NAME) 87 | 88 | options: 89 | @ printf "%-11s = %s\n" "CFLAGS" "$(CFLAGS)" 90 | @ printf "%-11s = %s\n" "HEADERS" "$(HEADERS)" 91 | @ printf "%-11s = %s\n" "SOURCES" "$(SOURCES)" 92 | @ printf "%-11s = %s\n" "OBJECTS" "$(OBJECTS)" 93 | @ printf "%-11s = %s\n" "COMM_OBJS" "$(COMM_OBJS)" 94 | @ printf "%-11s = %s\n" "COMP_OBJS" "$(COMP_OBJS)" 95 | @ printf "%-11s = %s\n" "RUN_OBJS" "$(RUN_OBJS)" 96 | @ printf "%-11s = %s\n" "TARGET_OBJS" "$(TARGET_OBJS)" 97 | 98 | x11: options 99 | x11: $(BUILD_DIR)/$(NAME) 100 | 101 | wayland: options 102 | wayland: $(WAY_FILES) 103 | wayland: $(BUILD_DIR)/$(NAME) 104 | 105 | debug: CFLAGS += -ggdb 106 | debug: all 107 | 108 | debug-x11: CFLAGS += -ggdb 109 | debug-x11: x11 110 | 111 | debug-wayland: CFLAGS += -ggdb 112 | debug-wayland: wayland 113 | 114 | $(BUILD_DIR)/$(NAME): $(OBJECTS) $(COMM_OBJS) $(COMP_OBJS) $(RUN_OBJS) $(TARGET_OBJS) 115 | @ printf "%s %s %s\n" $(CC) "$@ $^" "$(CFLAGS) $(LDFLAGS)" 116 | @ $(CC) $^ -o $@ $(CFLAGS) $(LDFLAGS) 117 | @ cp $@ $(NAME) 118 | 119 | $(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.c $(HEADERS) 120 | @ printf "%s %s %s\n" $(CC) $< "$(CFLAGS)" 121 | @ mkdir -p $(@D) 122 | @ $(CC) -c $(CFLAGS) -o $@ $< 123 | 124 | $(BUILD_DIR)/common/%.o: $(COMMON_DIR)/%.c 125 | @ printf "%s %s %s\n" $(CC) $< "$(CFLAGS) -iquote$(COMMON_DIR)" 126 | @ mkdir -p $(@D) 127 | @ $(CC) -c $(CFLAGS) -iquote$(COMMON_DIR) -o $@ $< 128 | 129 | $(BUILD_DIR)/compiler/%.o: $(COMPILER_DIR)/%.c 130 | @ printf "%s %s %s\n" $(CC) $< "$(CFLAGS) -iquote$(COMPILER_DIR)" 131 | @ mkdir -p $(@D) 132 | @ $(CC) -c $(CFLAGS) -iquote$(COMPILER_DIR) -o $@ $< 133 | 134 | $(BUILD_DIR)/runtime/%.o: $(RUNTIME_DIR)/%.c 135 | @ printf "%s %s %s\n" $(CC) $< "$(CFLAGS) -iquote$(RUNTIME_DIR)" 136 | @ mkdir -p $(@D) 137 | @ $(CC) -c $(CFLAGS) -iquote$(RUNTIME_DIR) -o $@ $< 138 | 139 | $(BUILD_DIR)/runtime/x11/%.o: $(X11_DIR)/%.c 140 | @ printf "%s %s %s\n" $(CC) $< "$(CFLAGS) $(X11_CFLAGS) -iquote$(X11_DIR)" 141 | @ mkdir -p $(@D) 142 | @ $(CC) -c $(CFLAGS) $(X11_CFLAGS) -iquote$(X11_DIR) -o $@ $< 143 | 144 | $(BUILD_DIR)/runtime/wayland/%.o: $(WAY_DIR)/%.c 145 | @ printf "%s %s %s\n" $(CC) $< "$(CFLAGS) $(WAY_CFLAGS) -iquote$(WAY_DIR)" 146 | @ mkdir -p $(@D) 147 | @ $(CC) -c $(CFLAGS) $(WAY_CFLAGS) -iquote$(WAY_DIR) -o $@ $< 148 | 149 | $(CONF_DIR)/config.h: 150 | cp $(CONF_DIR)/config.def.h $@ 151 | 152 | $(CONF_DIR)/key_chords.h: 153 | cp $(CONF_DIR)/key_chords.def.h $@ 154 | 155 | # Wayland-scanner rules 156 | $(WAY_DIR)/xdg-shell.c: 157 | wayland-scanner private-code < \ 158 | "$$($(PKG_CONFIG) --variable=pkgdatadir wayland-protocols)/stable/xdg-shell/xdg-shell.xml" \ 159 | > $@ 160 | 161 | $(WAY_DIR)/wlr-layer-shell-unstable-v1.h: $(WAY_DIR)/wlr-layer-shell-unstable-v1.xml 162 | wayland-scanner client-header < $^ > $@ 163 | 164 | $(WAY_DIR)/wlr-layer-shell-unstable-v1.c: $(WAY_DIR)/wlr-layer-shell-unstable-v1.xml 165 | wayland-scanner private-code < $^ > $@ 166 | 167 | # Manfile generation and cleanup 168 | $(MAN_DIR)/%.1: $(MAN_DIR)/%.1.man 169 | cp -f $< $@ 170 | sh clean_man_files.sh "General Commands Manual" "$@" 171 | 172 | $(MAN_DIR)/%.5: $(MAN_DIR)/%.5.man 173 | cp -f $< $@ 174 | sh clean_man_files.sh "File Formats Manual" "$@" 175 | 176 | clean: 177 | rm -rf $(BUILD_DIR) 178 | rm -f $(NAME) 179 | rm -f $(MAN_FILES:.man=) 180 | rm -f $(WAY_FILES) 181 | 182 | dist: clean 183 | mkdir -p $(NAME)-$(VERSION) 184 | cp -R src man config.def.h key_chords.def.h LICENSE Makefile README.md $(NAME)-$(VERSION) 185 | tar -czf $(NAME)-$(VERSION).tar.gz $(NAME)-$(VERSION) 186 | rm -rf $(NAME)-$(VERSION) 187 | 188 | install: $(BUILD_DIR)/$(NAME) $(MAN_FILES:.man=) 189 | mkdir -p $(DESTDIR)$(PREFIX)/bin 190 | cp -f $(NAME) $(DESTDIR)$(PREFIX)/bin 191 | chmod 755 $(DESTDIR)$(PREFIX)/bin/$(NAME) 192 | for section in 1 5; do \ 193 | mkdir -p $(DESTDIR)$(MANPREFIX)/man$${section}; \ 194 | cp -f $(MAN_DIR)/*.$$section $(DESTDIR)$(MANPREFIX)/man$${section}; \ 195 | chmod 644 $(DESTDIR)$(MANPREFIX)/man$${section}/$(NAME)*.$${section}; \ 196 | done 197 | 198 | man: $(MAN_FILES:.man=) 199 | 200 | uninstall: 201 | rm -f $(DESTDIR)$(PREFIX)/bin/$(NAME) 202 | for section in 1 5; do \ 203 | rm -f $(DESTDIR)$(MANPREFIX)/man$${section}/$(NAME)*.$${section}; \ 204 | done 205 | 206 | .PHONY: all debug x11 wayland debug-x11 debug-wayland clean dist install uninstall 207 | 208 | -include $(OBJECTS:.o=.d) $(COMM_OBJS:.o=.d) $(COMP_OBJS:.o=.d) $(RUN_OBJS:.o=.d) $(X11_OBJS:.o=.d) $(WAY_OBJS:.o=.d) 209 | -------------------------------------------------------------------------------- /clean_man_files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | file_type="${1:?missing file type}" 4 | file_name="${2:?missing file name}" 5 | 6 | # Generate the new date string 7 | new_date=$(date '+%B %d, %Y') 8 | 9 | fix_header="s/^\.TH \(\"[^\"]*\"\) \(\"[^\"]*\"\).*$/.TH \1 \2 \"$new_date\" \"\" \"$file_type\"/" 10 | # I write the man files in org and have to insert a zero-width-space 11 | # to get the exports to work correctly. They aren't an issue in the final 12 | # man pages, but they aren't needed either. Feel free to comment this line out. 13 | remove_zero_width_space="s/\xe2\x80\x8b//g" 14 | fix_bold_caret="s/\*\^\*\^/\\\\f[B]^\\\\f[R]/" 15 | fix_interp_caret="s/\*desc\\\\\[ha]\^\*\^/\\\\f[B]desc^^\\\\f[R]/;s/\*desc\^\*\^/\\\\f[B]desc^\\\\f[R]/" 16 | remove_surrounding_tilde="s/\~\([^\~ ]\+\)\~/_\1/g" 17 | 18 | # Update man heading with the new date and file type and remove zero-width-spaces 19 | sed -i \ 20 | -e "$fix_header" \ 21 | -e "$remove_zero_width_space" \ 22 | -e "$fix_bold_caret" \ 23 | -e "$fix_interp_caret" \ 24 | -e "$remove_surrounding_tilde" \ 25 | "$file_name" 26 | -------------------------------------------------------------------------------- /config/config.def.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_CONFIG_CONFIG_H_ 2 | #define WK_CONFIG_CONFIG_H_ 3 | 4 | #include 5 | 6 | /* menu include */ 7 | #include "src/common/menu.h" 8 | 9 | /* Delimiter when displaying chords. */ 10 | static const char* delimiter = " -> "; 11 | /* Delay between last keypress and first time displaying the menu. Value in milliseconds. */ 12 | static uint32_t delay = 1000; 13 | /* Max number of columns to use. */ 14 | static const uint32_t maxCols = 5; 15 | /* Menu width. Set to '-1' for 1/2 the width of your screen. */ 16 | static const int32_t menuWidth = -1; 17 | /* Menu gap between top/bottom of screen. Set to '-1' for a gap of 1/10th of the screen height. */ 18 | static const int32_t menuGap = -1; 19 | /* X-Padding around key/description text in cells. */ 20 | static const uint32_t widthPadding = 6; 21 | /* Y-Padding around key/description text in cells. */ 22 | static const uint32_t heightPadding = 2; 23 | /* Position to place the menu. '0' = bottom; '1' = top. */ 24 | static const uint32_t menuPosition = 0; 25 | /* Menu border width */ 26 | static const uint32_t borderWidth = 4; 27 | /* Menu border radius. 0 means no curve */ 28 | static const double borderRadius = 0; 29 | /* Menu foreground color */ 30 | static const char* foreground[FOREGROUND_COLOR_LAST] = { 31 | "#DCD7BA", /* Key color */ 32 | "#525259", /* Delimiter color */ 33 | "#AF9FC9", /* Prefix color */ 34 | "#DCD7BA", /* Chord color */ 35 | }; 36 | /* Menu background color */ 37 | static const char* background = "#181616"; 38 | /* Menu border color */ 39 | static const char* border = "#7FB4CA"; 40 | /* Default shell to run chord commands with. */ 41 | static const char* shell = "/bin/sh"; 42 | /* Pango font description i.e. 'Noto Mono, M+ 1c, ..., 16'. */ 43 | static const char* font = "monospace, 14"; 44 | /* Keys to use for chord arrays */ 45 | static const char* implicitArrayKeys = "asdfghjkl;"; 46 | 47 | #endif /* WK_CONFIG_CONFIG_H_ */ 48 | -------------------------------------------------------------------------------- /config/key_chords.def.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_CONFIG_KEY_CHORDS_H_ 2 | #define WK_CONFIG_KEY_CHORDS_H_ 3 | 4 | #include 5 | 6 | #include "src/common/key_chord.h" 7 | 8 | /* state, 9 | * KEY( 10 | * mods, 11 | * special, 12 | * key, key_len 13 | * ), 14 | * description, 15 | * command 16 | * before 17 | * after 18 | * flags, chords 19 | */ 20 | KeyChord builtinKeyChords[] = { 21 | { 22 | .state = KEY_CHORD_STATE_NOT_NULL, 23 | .key = { 24 | .mods = { 25 | .ctrl = false, .alt = false, .hyper = false, .shift = false 26 | }, 27 | .special = SPECIAL_KEY_NONE, 28 | .repr = "a", .len = 1 29 | }, 30 | .description = "A chord", 31 | .command = "echo \"Hello, world!\"", 32 | .before = NULL, 33 | .after = NULL, 34 | .flags = { 35 | false, false, false, false, false, false, false, 36 | false, false, false, false, false, false, false 37 | }, .keyChords = NULL 38 | }, 39 | { 40 | .state = KEY_CHORD_STATE_NOT_NULL, 41 | .key = { 42 | .mods = { 43 | .ctrl = false, .alt = false, .hyper = false, .shift = false 44 | }, 45 | .special = SPECIAL_KEY_NONE, 46 | .repr = "p", .len = 1 47 | }, 48 | .description = "A prefix", 49 | .command = NULL, 50 | .before = NULL, 51 | .after = NULL, 52 | .flags = { 53 | false, false, false, false, false, false, false, 54 | false, false, false, false, false, false, false 55 | }, 56 | .keyChords = (KeyChord[]){ 57 | { 58 | .state = KEY_CHORD_STATE_NOT_NULL, 59 | .key = { 60 | .mods = { 61 | .ctrl = false, .alt = false, .hyper = false, .shift = false 62 | }, 63 | .special = SPECIAL_KEY_NONE, 64 | .repr = "b", .len = 1 65 | }, 66 | .description = "A chord", 67 | .command = "echo \"Hello from inside prefix 'C-a'\"", 68 | .before = NULL, 69 | .after = NULL, 70 | .flags = { 71 | false, false, false, false, false, false, false, 72 | false, false, false, false, false, false, false 73 | }, .keyChords = NULL 74 | }, 75 | { 76 | .state = KEY_CHORD_STATE_NOT_NULL, 77 | .key = { 78 | .mods = { 79 | .ctrl = false, .alt = false, .hyper = false, .shift = false 80 | }, 81 | .special = SPECIAL_KEY_NONE, 82 | .repr = "c", .len = 1 83 | }, 84 | .description = "Another prefix", 85 | .command = NULL, 86 | .before = NULL, 87 | .after = NULL, 88 | .flags = { 89 | false, false, false, false, false, false, false, 90 | false, false, false, false, false, false, false 91 | }, 92 | .keyChords = (KeyChord[]){ 93 | { 94 | .state = KEY_CHORD_STATE_NOT_NULL, 95 | .key = { 96 | .mods = { 97 | .ctrl = false, .alt = false, .hyper = false, .shift = false 98 | }, 99 | .special = SPECIAL_KEY_NONE, 100 | .repr = "d", .len = 1 101 | }, 102 | .description = "Done", 103 | .command = "echo \"You've reached the end!\"", 104 | .before = NULL, 105 | .after = NULL, 106 | .flags = { 107 | false, false, false, false, false, false, false, 108 | false, false, false, false, false, false, false 109 | }, .keyChords = NULL 110 | }, 111 | { .state = KEY_CHORD_STATE_IS_NULL } 112 | } 113 | }, 114 | { .state = KEY_CHORD_STATE_IS_NULL } 115 | } 116 | }, 117 | { .state = KEY_CHORD_STATE_IS_NULL } 118 | }; 119 | 120 | #endif /* WK_CONFIG_KEY_CHORDS_H_ */ 121 | -------------------------------------------------------------------------------- /examples/basic_example.wks: -------------------------------------------------------------------------------- 1 | # This is a comment 2 | # This is also a comment 3 | 4 | # This is a chord. 5 | # The trigger key is 'a'. 6 | # The description is "A chord". 7 | # The command is 'echo "Hello, World"'. 8 | a "A chord" %{{echo "Hello, world!"}} 9 | 10 | # This is a prefix. 11 | # The trigger key is 'Control + a' 12 | p "A prefix" 13 | { 14 | # This is a chord that can only be accessed after triggering the parent prefix. 15 | b "A chord" %{{echo "Hello from inside prefix 'C-a'"}} 16 | 17 | # Prefixes can nest additional prefixes arbitrarily deep 18 | c "Another prefix" 19 | { 20 | d "Done" %{{echo "You've reached the end!"}} 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/chord_array_example.wks: -------------------------------------------------------------------------------- 1 | ... "Switch %(index+1)" +write %{{switching to %(index)}} 2 | [xyz] "Switch %(index+1)" +write %{{switching to %(index)}} 3 | C-M-... "Switch %(index+1)" +write %{{switching to %(index)}} 4 | -------------------------------------------------------------------------------- /examples/chord_expressions.wks: -------------------------------------------------------------------------------- 1 | # Client binds 2 | c "Client" 3 | { 4 | # Tag & follow prefix 5 | m "Tag & follow" ^before %{{dwmc tagex %(index)}} +keep 6 | { 7 | [ 8 | arst 9 | (g "special key" +unhook %{{echo a special command}}) 10 | mnei 11 | ] "Tag %(index+1)" %{{dwmc viewex %(index)}} 12 | } 13 | q "Quit" %{{sleep 0.1 ; dwmc killclient}} 14 | s "Sticky" %{{dwmc togglesticky}} 15 | # Tag prefix 16 | t "Tag" +keep 17 | { 18 | [arstgmnei] "Tag %(index+1)" %{{dwmc tagex %(index)}} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/delimiter_examples.wks: -------------------------------------------------------------------------------- 1 | # Commands with opening and closing delimiters 2 | a "{{" %{{echo "hello, world"}} 3 | b "((" %((echo "hello, world")) 4 | c "[[" %[[echo "hello, world"]] 5 | 6 | # Valid alternatives 7 | d "||" %||echo "hello, world"|| 8 | e "%%" %%%echo "hello, world"%% 9 | f "zz" %zzecho "hello, world"zz 10 | -------------------------------------------------------------------------------- /examples/error_example.wks: -------------------------------------------------------------------------------- 1 | a "An invalid Chord" -write %{{Look at all these errors!}} 2 | b "A valid Chord" +write %{{That looks better}} 3 | a "Bad prefix" -unhook 4 | { 5 | [bcd] "Good %(key)" +write %{{nice a quiet.}} 6 | } 7 | # Invalid key 8 | @ "Invalid key" %{{echo "This should fail"}} 9 | 10 | l "+l" 11 | { 12 | n "n +write" %{{hmmm}} 13 | } 14 | 15 | # Unterminated string 16 | a "Unterminated string %{{echo "This should also fail}} 17 | 18 | # Invalid interpolation 19 | b "Invalid interpolation" %{{echo "%(invalid)"}} 20 | 21 | # Unexpected token 22 | c +invalid %{{echo "This should fail"}} 23 | -------------------------------------------------------------------------------- /examples/extended_example.wks: -------------------------------------------------------------------------------- 1 | # Some chord expressions 2 | [ 3 | abc 4 | (d "What will happen..." :keep %{{echo "Well??"}}) 5 | efg 6 | ] "Let us see" %{{echo "here we go!"}} 7 | # Hmm... 8 | h "H prefix" 9 | { 10 | [ 11 | abc 12 | (d "...") 13 | efg 14 | ] "..." %{{echo "well.."}} 15 | } 16 | # Hmm... 17 | i "I wonder prefix" 18 | { 19 | j "..." 20 | { 21 | [ 22 | abc 23 | d 24 | d 25 | d 26 | d 27 | d 28 | d 29 | d 30 | efg 31 | ] "..." %{{echo "well.."}} 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/include_examples/browser_key_chords.wks: -------------------------------------------------------------------------------- 1 | [ 2 | (b "Brave") 3 | (c "Chrome") 4 | (f "Firefox") 5 | ] "null" +write %{{%(desc,)}} # downcase the description and use as the command 6 | 7 | # Mullvad-exclude prefix 8 | m "+Mullvad Exclude" 9 | { 10 | [ 11 | (b "Brave") 12 | (c "Chrome") 13 | (f "Firefox") 14 | ] "null" %{{mullvad-exclude %(desc,)}} 15 | } 16 | -------------------------------------------------------------------------------- /examples/include_examples/emacs_key_chords.wks: -------------------------------------------------------------------------------- 1 | b "Open blank" %{{emacsclient -c -a ""}} 2 | p "+Projects" 3 | { 4 | w "wk" %{{emacs "~/Projects/wk"}} 5 | } 6 | -------------------------------------------------------------------------------- /examples/include_examples/main.wks: -------------------------------------------------------------------------------- 1 | # Browser prefix 2 | b "+Browser" { :include "browser_key_chords.wks" } 3 | # Emacs prefix 4 | e "+Emacs" ^before %{{dwmc viewex 1}} { :include "emacs_key_chords.wks" } 5 | # mpc prefix 6 | m "+mpc" +keep { :include "mpc_key_chords.wks" } 7 | -------------------------------------------------------------------------------- /examples/include_examples/mpc_key_chords.wks: -------------------------------------------------------------------------------- 1 | c "Clear mpc" %{{mpc clear}} 2 | d "Display song" %{{songinfo}} 3 | h "Seek -5s" %{{mpc seek "-5"}} 4 | l "Seek +5s" %{{mpc seek "+5"}} 5 | n "Next song" %{{mpc next}} 6 | p "Prev song" %{{mpc prev}} 7 | o "Open mpc" +ignore %{{term -e ncmpcpp}} 8 | -------------------------------------------------------------------------------- /examples/logo_example.wks: -------------------------------------------------------------------------------- 1 | :menu-width 550 2 | :font "monospace, 30" 3 | [ 4 | (w "which") 5 | (k "key") 6 | ] "(null)" +write %{{%(key)}} 7 | -------------------------------------------------------------------------------- /examples/long_text_example.wks: -------------------------------------------------------------------------------- 1 | a "👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 2 | r "👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 3 | s "👍👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 4 | t "👍👍👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 5 | g "👍👍👍👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 6 | m "👍👍👍👍👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 7 | n "👍👍👍👍👍👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 8 | e "👍👍👍👍👍👍👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 9 | i "👍👍👍👍👍👍👍👍👍n👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 10 | o "👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍👍ng text for this chord '%(key)'" +write %{{hmm...}} 11 | -------------------------------------------------------------------------------- /examples/preprocessor_example.wks: -------------------------------------------------------------------------------- 1 | :include "upper_and_lower_example.wks" 2 | :shell "/bin/zsh" 3 | :font "Sans, 14" 4 | :bg "#123456" 5 | :top a "hello!" +write %{{echo %(desc) %(key)}} 6 | :menu-gap 1 7 | :border-width 1 8 | -------------------------------------------------------------------------------- /examples/self.wks: -------------------------------------------------------------------------------- 1 | # This is not allowed. 2 | :include "self.wks" 3 | -------------------------------------------------------------------------------- /examples/sorted_example.wks: -------------------------------------------------------------------------------- 1 | [neio] "Switch %(index+1)" %{{xdotool set_desktop %(index)}} 2 | b "Second?" +write %{{%(index)}} 3 | a "First?" +write %{{%(index)}} 4 | -------------------------------------------------------------------------------- /examples/sorted_with_ignore_sort_example.wks: -------------------------------------------------------------------------------- 1 | [neio] "Switch %(index+1)" +ignore-sort %{{xdotool set_desktop %(index)}} 2 | b "Second?" +write %{{%(index)}} 3 | a "First?" +write %{{%(index)}} 4 | -------------------------------------------------------------------------------- /examples/special_keys_example.wks: -------------------------------------------------------------------------------- 1 | Left "Move left" %{{echo "Moving left"}} 2 | Right "Move right" %{{echo "Moving right"}} 3 | Up "Move up" %{{echo "Moving up"}} 4 | Down "Move down" %{{echo "Moving down"}} 5 | TAB "Tab" %{{echo "Tabbing"}} 6 | SPC "Space" %{{echo "Spacing"}} 7 | RET "Return" %{{echo "Returning"}} 8 | DEL "Delete" %{{echo "Deleting"}} 9 | ESC "Escape" %{{echo "Escaping"}} 10 | Home "Home" %{{echo "Going home"}} 11 | PgUp "Page up" %{{echo "Paging up"}} 12 | PgDown "Page down" %{{echo "Paging down"}} 13 | End "End" %{{echo "Going to the end"}} 14 | Begin "Begin" %{{echo "Beginning"}} 15 | -------------------------------------------------------------------------------- /examples/truncation_test.wks: -------------------------------------------------------------------------------- 1 | a "👋🌍😊👀🌸📱📖🎉🎊👍💐🌺🌹🌷🌼" +write %{{hemlo}} 2 | a "🌻🪴🍀🍁🍂🍃🌿☘️🍀🍁🍂🍃🌿☘️🍀" +write %{{hemlo}} 3 | a "🍁🍂🍃🌿☘️🍀🍁🍂🍃🌿☘️🍀🍁🍂🍃" +write %{{hemlo}} 4 | a "🌿☘️🍀🍁🍂🍃🌿☘️🍀🍁🍂🍃🌿☘️🍀" +write %{{hemlo}} 5 | a "🍁🍂🍃🌿☘️🍀🍁🍂🍃🌿☘️🍀🍁🍂🍃" +write %{{hemlo}} 6 | a "🌿☘️🍀🍁🍂🍃🌿☘️🍀🍁🍂🍃🌿☘️🍀" +write %{{hemlo}} 7 | a "こんにちは世界 bonjour le" +write %{{hemlo}} 8 | a "monde hola mundo привет" +write %{{hemlo}} 9 | a "мир 👋🌍 hello world 😊🌸" +write %{{hemlo}} 10 | a "你好世界 안녕하세요 세계 😊👀" +write %{{hemlo}} 11 | a "ض🌍 hello 👋 there حكم" +write %{{hemlo}} 12 | a "عن ال😊حياة здравствуйте" +write %{{hemlo}} 13 | a "все привет 안녕하세요 여🌸" +write %{{hemlo}} 14 | a "러분 こんにちは皆さ🎉ん" +write %{{hemlo}} 15 | a "bonjour à tous 你好大家" +write %{{hemlo}} 16 | a "hola 👀🌍 a todos 📕📱😊" +write %{{hemlo}} 17 | a "Viele Grüße und 📖 einen" +write %{{hemlo}} 18 | a "schönen Tag 🌸😀" +write %{{hemlo}} 19 | a "ευχαριστώ για την προσοχή" +write %{{hemlo}} 20 | a "👀σας มีความสุข🎊กับวันนี้" +write %{{hemlo}} 21 | a "สวัสดีครับ ขอให้มีความสุข😊" +write %{{hemlo}} 22 | a "มาก ๆ เลยนะครับ chào mọi" +write %{{hemlo}} 23 | a "👋🌍 người xin chào buổi" +write %{{hemlo}} 24 | a "sáng guten 😊 Tag" +write %{{hemlo}} 25 | a "zusammen добрый день" +write %{{hemlo}} 26 | a "всем 🌍 안녕하세요 여러분" +write %{{hemlo}} 27 | a "좋은 👀 아침입니다 今日は皆" +write %{{hemlo}} 28 | a "さんに👋 素敵な一日を" +write %{{hemlo}} 29 | a "bonjour 📱 tout le monde" +write %{{hemlo}} 30 | a "buenos días a todos" +write %{{hemlo}} 31 | a "привет как дела 你们好吗" +write %{{hemlo}} 32 | a "สวัสดีค่ะ สบายดี🌸ไหมคะ" +write %{{hemlo}} 33 | a "chào buổi 👋 chiều tất cả" +write %{{hemlo}} 34 | a "mọi người 안녕하세요 여러분" +write %{{hemlo}} 35 | a "오늘도 좋은 하루 👀 되세요" +write %{{hemlo}} 36 | a "今日もいい一日を過ごしてください" +write %{{hemlo}} 37 | a "bonjour 😊 à tous bonne" +write %{{hemlo}} 38 | a "journée 🎉🌸😊👍👀🌍🎂🎈🎁🎊" +write %{{hemlo}} 39 | a "🎋🎍🎑🎎🎏🎐🎀🎗🎟🎫🎖🏆🥇🥈🥉" +write %{{hemlo}} 40 | -------------------------------------------------------------------------------- /examples/unicode_example.wks: -------------------------------------------------------------------------------- 1 | ä "Umlaut" %{{echo "Umlaut"}} 2 | 👍 "Thumbs up" %{{echo "Thumbs up"}} 3 | -------------------------------------------------------------------------------- /examples/upper_and_lower_example.wks: -------------------------------------------------------------------------------- 1 | l "LOWER FIRST" +write %{{%(desc,)}} 2 | L "LOWER ALL" +write %{{%(desc,,)}} 3 | u "upper first" +write %{{%(desc^)}} 4 | U "upper all" +write %{{%(desc^^)}} 5 | -------------------------------------------------------------------------------- /man/wk.1.man: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pandoc 3.1.8 2 | .\" 3 | .TH "WK" "1" "" "" "" 4 | .SH NAME 5 | wk - Which-Key via X11 and Wayland, inspired by \f[B]dmenu\f[R] and 6 | \f[B]bemenu\f[R] respectively. 7 | .SH SYNOPSIS 8 | \f[B]wk\f[R] [options] 9 | .SH DESCRIPTION 10 | \f[B]wk\f[R] provides visual feedback when executing user defined key 11 | chords via a popup menu. 12 | The key chords may be precompiled into \f[B]wk\f[R] for users who 13 | maintain their own copy of \f[B]wk\f[R]. 14 | For users looking for a more dynamic experience, \f[B]wk\f[R] can read 15 | key chords from a \f[B]wks\f[R]​(5) file or script. 16 | .SH OPTIONS 17 | .TP 18 | \f[B]-h, \[en]help\f[R] 19 | Display help message and exit. 20 | .TP 21 | \f[B]-v, \[en]version\f[R] 22 | Display version number and exit. 23 | .TP 24 | \f[B]-d, \[en]debug\f[R] 25 | Print debug information during execution. 26 | .TP 27 | \f[B]-D, \[en]delay\f[R] \f[I]INT\f[R] 28 | Delay the popup menu by \f[I]INT\f[R] milliseconds from startup or last 29 | keypress (default 1000 ms). 30 | .TP 31 | \f[B]-t, \[en]top\f[R] 32 | Position menu at top of screen. 33 | .TP 34 | \f[B]-b, \[en]bottom\f[R] 35 | Position menu at bottom of screen. 36 | .TP 37 | \f[B]-s, \[en]script\f[R] 38 | Read \f[B]wks\f[R] script from stdin to use as key chords. 39 | .TP 40 | \f[B]-S, \[en]sort\f[R] 41 | Sort key chords read from \f[B]\[en]key-chords, \[en]script, or 42 | \[en]transpile\f[R]. 43 | .TP 44 | \f[B]-m, \[en]max-columns\f[R] \f[I]INT\f[R] 45 | Set the maximum menu columns to \f[I]INT\f[R] (default 5). 46 | .TP 47 | \f[B]-p, \[en]press\f[R] \f[I]KEY(s)\f[R] 48 | Press \f[I]KEY(s)\f[R] before dispalying menu. 49 | See \f[B]TRIGGER KEY\f[R] in the \f[B]wks\f[R] man page for more info. 50 | .TP 51 | \f[B]-T, \[en]transpile\f[R] \f[I]FILE\f[R] 52 | Transpile \f[I]FILE\f[R] to valid \[aq]key~chords~.h\[aq] syntax and 53 | print to stdout. 54 | .TP 55 | \f[B]-k, \[en]key-chords\f[R] \f[I]FILE\f[R] 56 | Use \f[I]FILE\f[R] for key chords rather than those precompiled. 57 | .TP 58 | \f[B]-w, \[en]menu-width\f[R] \f[I]INT\f[R] 59 | Set menu width to \f[I]INT\f[R]. 60 | Set to \[aq]-1\[aq] for a width equal to 1/2 of the screen width 61 | (default -1). 62 | .TP 63 | \f[B]-g, \[en]menu-gap\f[R] \f[I]INT\f[R] 64 | Set menu gap between top/bottom of screen to \f[I]INT\f[R]. 65 | Set to \[aq]-1\[aq] for a gap equal to 1/10th of the screen height 66 | (default -1). 67 | .TP 68 | \f[B]\[en]border-width\f[R] \f[I]INT\f[R] 69 | Set border width to \f[I]INT\f[R] (default 4). 70 | .TP 71 | \f[B]\[en]border-radius\f[R] \f[I]NUM\f[R] 72 | Set border radius to \f[I]NUM\f[R] degrees. 73 | 0 means no curve (default 0). 74 | .TP 75 | \f[B]\[en]wpadding\f[R] \f[I]INT\f[R] 76 | Set left and right padding around hint text to \f[I]INT\f[R] (default 77 | 6). 78 | .TP 79 | \f[B]\[en]hpadding\f[R] \f[I]INT\f[R] 80 | Set up and down padding around hint text to \f[I]INT\f[R]. 81 | (default 2) 82 | .TP 83 | \f[B]\[en]fg\f[R] \f[I]COLOR\f[R] 84 | Set all menu foreground text to \f[I]COLOR\f[R] where color is some hex 85 | string i.e. 86 | \[aq]#F1CD39\[aq] (default unset). 87 | .TP 88 | \f[B]\[en]fg-key\f[R] \f[I]COLOR\f[R] 89 | Set foreground key to COLOR (default \[aq]#DCD7BA\[aq]). 90 | .TP 91 | \f[B]\[en]fg-delimiter\f[R] \f[I]COLOR\f[R] 92 | Set foreground delimiter to COLOR (default \[aq]#525259\[aq]). 93 | .TP 94 | \f[B]\[en]fg-prefix\f[R] \f[I]COLOR\f[R] 95 | Set foreground prefix to COLOR (default \[aq]#AF9FC9\[aq]). 96 | .TP 97 | \f[B]\[en]fg-chord\f[R] \f[I]COLOR\f[R] 98 | Set foreground chord to COLOR (default \[aq]#DCD7BA\[aq]). 99 | .TP 100 | \f[B]\[en]bg\f[R] \f[I]COLOR\f[R] 101 | Set background to COLOR (default \[aq]#181616\[aq]). 102 | .TP 103 | \f[B]\[en]bd\f[R] \f[I]COLOR\f[R] 104 | Set border to COLOR (default \[aq]#7FB4CA\[aq]). 105 | .TP 106 | \f[B]\[en]shell\f[R] \f[I]STRING\f[R] 107 | Set shell to STRING (default \[aq]/bin/sh\[aq]). 108 | .TP 109 | \f[B]\[en]font\f[R] \f[I]STRING\f[R] 110 | Set font to STRING. 111 | Should be a valid Pango font description (default \[aq]monospace, 112 | 14\[aq]). 113 | .SH BUG REPORTS 114 | If you find a bug in \f[B]wk\f[R], please report it at 115 | . 116 | .SH SEE ALSO 117 | \f[B]wks\f[R]​(5) 118 | .SH AUTHORS 119 | 3L0C . 120 | -------------------------------------------------------------------------------- /man/wk.1.org: -------------------------------------------------------------------------------- 1 | #+title: WK(1) 2 | #+author: 3L0C 3 | 4 | * NAME 5 | 6 | wk - Which-Key via X11 and Wayland, inspired by *dmenu* and 7 | *bemenu* respectively. 8 | 9 | * SYNOPSIS 10 | 11 | *wk* [options] 12 | 13 | * DESCRIPTION 14 | 15 | *wk* provides visual feedback when executing user defined 16 | key chords via a popup menu. The key chords may be 17 | precompiled into *wk* for users who maintain their own copy 18 | of *wk*. For users looking for a more dynamic experience, 19 | *wk* can read key chords from a *wks*​(5) file or script. 20 | 21 | * OPTIONS 22 | 23 | - *-h, --help* :: 24 | Display help message and exit. 25 | 26 | - *-v, --version* :: 27 | Display version number and exit. 28 | 29 | - *-d, --debug* :: 30 | Print debug information during execution. 31 | 32 | - *-D, --delay* /INT/ :: 33 | Delay the popup menu by /INT/ milliseconds from startup or 34 | last keypress (default 1000 ms). 35 | 36 | - *-t, --top* :: 37 | Position menu at top of screen. 38 | 39 | - *-b, --bottom* :: 40 | Position menu at bottom of screen. 41 | 42 | - *-s, --script* :: 43 | Read *wks* script from stdin to use as key chords. 44 | 45 | - *-S, --sort* :: 46 | Sort key chords read from *--key-chords, --script, or --transpile*. 47 | 48 | - *-m, --max-columns* /INT/ :: 49 | Set the maximum menu columns to /INT/ (default 5). 50 | 51 | - *-p, --press* /KEY(s)/ :: 52 | Press /KEY(s)/ before dispalying menu. See *TRIGGER KEY* 53 | in the *wks* man page for more info. 54 | 55 | - *-T, --transpile* /FILE/ :: 56 | Transpile /FILE/ to valid 'key_chords.h' syntax and print to stdout. 57 | 58 | - *-k, --key-chords* /FILE/ :: 59 | Use /FILE/ for key chords rather than those precompiled. 60 | 61 | - *-w, --menu-width* /INT/ :: 62 | Set menu width to /INT/. Set to '-1' for a width equal to 63 | 1/2 of the screen width (default -1). 64 | 65 | - *-g, --menu-gap* /INT/ :: 66 | Set menu gap between top/bottom of screen to /INT/. Set to 67 | '-1' for a gap equal to 1/10th of the screen height 68 | (default -1). 69 | 70 | - *--border-width* /INT/ :: 71 | Set border width to /INT/ (default 4). 72 | 73 | - *--border-radius* /NUM/ :: 74 | Set border radius to /NUM/ degrees. 0 means no curve 75 | (default 0). 76 | 77 | - *--wpadding* /INT/ :: 78 | Set left and right padding around hint text to /INT/ 79 | (default 6). 80 | 81 | - *--hpadding* /INT/ :: 82 | Set up and down padding around hint text to /INT/. 83 | (default 2) 84 | 85 | - *--fg* /COLOR/ :: 86 | Set all menu foreground text to /COLOR/ where color is 87 | some hex string i.e. '#F1CD39' (default unset). 88 | 89 | - *--fg-key* /COLOR/ :: 90 | Set foreground key to COLOR (default '#DCD7BA'). 91 | 92 | - *--fg-delimiter* /COLOR/ :: 93 | Set foreground delimiter to COLOR (default '#525259'). 94 | 95 | - *--fg-prefix* /COLOR/ :: 96 | Set foreground prefix to COLOR (default '#AF9FC9'). 97 | 98 | - *--fg-chord* /COLOR/ :: 99 | Set foreground chord to COLOR (default '#DCD7BA'). 100 | 101 | - *--bg* /COLOR/ :: 102 | Set background to COLOR (default '#181616'). 103 | 104 | - *--bd* /COLOR/ :: 105 | Set border to COLOR (default '#7FB4CA'). 106 | 107 | - *--shell* /STRING/ :: 108 | Set shell to STRING (default '/bin/sh'). 109 | 110 | - *--font* /STRING/ :: 111 | Set font to STRING. Should be a valid Pango font 112 | description (default 'monospace, 14'). 113 | 114 | * BUG REPORTS 115 | If you find a bug in *wk*, please report it at 116 | https://github.com/3L0C/wk. 117 | 118 | * SEE ALSO 119 | *wks*​(5) 120 | -------------------------------------------------------------------------------- /src/common/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* local */ 10 | #include "common.h" 11 | #include "memory.h" 12 | 13 | void 14 | errorMsg(const char* fmt, ...) 15 | { 16 | assert(fmt); 17 | 18 | fprintf(stderr, "[ERROR] "); 19 | int len = strlen(fmt); /* 1 = '\0' */ 20 | va_list ap; 21 | va_start(ap, fmt); 22 | vfprintf(stderr, fmt, ap); 23 | va_end(ap); 24 | 25 | fputc((fmt[len - 1] == ':' ? ' ' : '\n'), stderr); 26 | } 27 | 28 | bool 29 | isUtf8ContByte(char byte) 30 | { 31 | return (byte & 0xC0) == 0x80; 32 | } 33 | 34 | bool 35 | isUtf8StartByte(char byte) 36 | { 37 | return (byte & 0xC0) != 0x80; 38 | } 39 | 40 | bool 41 | isUtf8MultiByteStartByte(char byte) 42 | { 43 | return (byte & 0x80) == 0x80 && (byte & 0xC0) != 0x80; 44 | } 45 | 46 | char* 47 | readFile(const char* filepath) 48 | { 49 | assert(filepath); 50 | 51 | FILE* file = fopen(filepath, "rb"); 52 | if (!file) 53 | { 54 | errorMsg("Could not open file '%s'.", filepath); 55 | goto fail; 56 | } 57 | 58 | fseek(file, 0L, SEEK_END); 59 | size_t fileSize = ftell(file); 60 | rewind(file); 61 | 62 | char* buffer = ALLOCATE(char, fileSize + 1); 63 | if (!buffer) 64 | { 65 | errorMsg("Not enough memory to read '%s'.", filepath); 66 | goto alloc_error; 67 | } 68 | 69 | size_t bytesRead = fread(buffer, sizeof(char), fileSize, file); 70 | if (bytesRead < fileSize) 71 | { 72 | errorMsg("Could not read file '%s'.", filepath); 73 | goto read_error; 74 | } 75 | 76 | buffer[bytesRead] = '\0'; 77 | fclose(file); 78 | 79 | return buffer; 80 | 81 | read_error: 82 | free(buffer); 83 | alloc_error: 84 | fclose(file); 85 | fail: 86 | return NULL; 87 | } 88 | 89 | void 90 | warnMsg(const char* fmt, ...) 91 | { 92 | assert(fmt); 93 | 94 | fprintf(stderr, "[WARNING] "); 95 | int len = strlen(fmt); /* 1 = '\0' */ 96 | va_list ap; 97 | va_start(ap, fmt); 98 | vfprintf(stderr, fmt, ap); 99 | va_end(ap); 100 | 101 | fputc((fmt[len - 1] == ':' ? ' ' : '\n'), stderr); 102 | } 103 | -------------------------------------------------------------------------------- /src/common/common.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMMON_COMMON_H_ 2 | #define WK_COMMON_COMMON_H_ 3 | 4 | #include 5 | 6 | void errorMsg(const char* fmt, ...); 7 | bool isUtf8ContByte(char byte); 8 | bool isUtf8MultiByteStartByte(char byte); 9 | bool isUtf8StartByte(char byte); 10 | char* readFile(const char* filepath); 11 | void warnMsg(const char* fmt, ...); 12 | 13 | #endif /* WK_COMMON_COMMON_H_ */ 14 | -------------------------------------------------------------------------------- /src/common/debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "common.h" 9 | #include "debug.h" 10 | #include "menu.h" 11 | #include "key_chord.h" 12 | 13 | static const int MAX_HEADER_WIDTH = 80; 14 | static const int DEBUG_SPACE = 9; /* '[DEBUG] |' == 9 */ 15 | static const char* DASHES = "-----------------------------------------------------------------------"; 16 | 17 | void 18 | debugMsg(bool debug, const char* fmt, ...) 19 | { 20 | if (!debug) return; 21 | 22 | assert(fmt); 23 | 24 | printf("[DEBUG] "); 25 | int len = strlen(fmt); /* 1 = '\0' */ 26 | va_list ap; 27 | va_start(ap, fmt); 28 | vprintf(fmt, ap); 29 | va_end(ap); 30 | 31 | fputc((fmt[len - 1] == ':' ? ' ' : '\n'), stdout); 32 | } 33 | 34 | void 35 | debugMsgWithIndent(int indent, const char* fmt, ...) 36 | { 37 | assert(fmt); 38 | 39 | printf("[DEBUG] "); 40 | for (int i = 0; i < indent; i++) 41 | { 42 | printf("| "); 43 | } 44 | 45 | size_t len = strlen(fmt); 46 | va_list ap; 47 | va_start(ap, fmt); 48 | vprintf(fmt, ap); 49 | va_end(ap); 50 | if (fmt[len - 1] != ' ') 51 | { 52 | fputc('\n', stdout); 53 | } 54 | } 55 | 56 | void 57 | debugPrintHeader(const char* header) 58 | { 59 | assert(header); 60 | 61 | int headerLen = strlen(header); /* add a space to each side */ 62 | if (headerLen > (MAX_HEADER_WIDTH - DEBUG_SPACE)) 63 | { 64 | debugMsg(true, "| %s", header); 65 | return; 66 | } 67 | else if (headerLen == 0) 68 | { 69 | debugMsg(true, "|%s\n", DASHES); 70 | return; 71 | } 72 | 73 | int dashCount = (MAX_HEADER_WIDTH - DEBUG_SPACE - headerLen); 74 | int leftDashes = dashCount / 2; 75 | int rightDashes = dashCount - leftDashes; 76 | debugMsg(true, "|%.*s%s%.*s", leftDashes, DASHES, header, rightDashes, DASHES); 77 | } 78 | 79 | void 80 | debugPrintHeaderWithIndent(int indent, const char* header) 81 | { 82 | assert(header); 83 | 84 | int headerLen = strlen(header); /* add a space to each side */ 85 | if (headerLen > (MAX_HEADER_WIDTH - DEBUG_SPACE)) 86 | { 87 | debugMsgWithIndent(indent, "| %s", header); 88 | return; 89 | } 90 | else if (headerLen == 0) 91 | { 92 | debugMsgWithIndent(indent, "|%s\n", DASHES); 93 | return; 94 | } 95 | 96 | int dashCount = (MAX_HEADER_WIDTH - DEBUG_SPACE - headerLen); 97 | int leftDashes = dashCount / 2; 98 | int rightDashes = dashCount - leftDashes; 99 | debugMsgWithIndent(indent, "|%.*s%s%.*s", leftDashes, DASHES, header, rightDashes, DASHES); 100 | } 101 | 102 | void 103 | debugTextWithLineNumber(const char* text) 104 | { 105 | if (!text) return; 106 | 107 | const char* current = text; 108 | size_t i = 1; 109 | 110 | while (*current != '\0') 111 | { 112 | printf("[DEBUG] | %4zu | ", i++); 113 | while (*current != '\n' && *current != '\0') 114 | { 115 | printf("%c", *current++); 116 | } 117 | if (*current == '\n') printf("%c", *current++); 118 | } 119 | } 120 | 121 | void 122 | debugTextLenWithLineNumber(const char* text, size_t len) 123 | { 124 | if (!text) return; 125 | 126 | const char* current = text; 127 | size_t i = 1; 128 | 129 | while (current < text + len) 130 | { 131 | printf("[DEBUG] | %4zu | ", i++); 132 | while (*current != '\n' && current < text + len) 133 | { 134 | printf("%c", *current++); 135 | } 136 | if (*current == '\n' && current < text + len) printf("%c", *current++); 137 | } 138 | if (*(current - 1) != '\n') printf("\n"); 139 | } 140 | 141 | void 142 | disassembleGrid( 143 | uint32_t startx, uint32_t starty, uint32_t rows, uint32_t cols, uint32_t wpadding, 144 | uint32_t hpadding, uint32_t cellw, uint32_t cellh, uint32_t count) 145 | { 146 | debugPrintHeader(" Grid "); 147 | debugMsgWithIndent(0, "|"); 148 | debugMsgWithIndent(0, "| Start X: %04u", startx, 0); 149 | debugMsgWithIndent(0, "| Start Y: %04u", starty); 150 | debugMsgWithIndent(0, "| Rows: %04u", rows); 151 | debugMsgWithIndent(0, "| Columns: %04u", cols); 152 | debugMsgWithIndent(0, "| Width padding: %04u", wpadding); 153 | debugMsgWithIndent(0, "| Height padding: %04u", hpadding); 154 | debugMsgWithIndent(0, "| Cell width: %04u", cellw); 155 | debugMsgWithIndent(0, "| Cell height: %04u", cellh); 156 | debugMsgWithIndent(0, "| Count: %04u", count); 157 | debugMsgWithIndent(0, "|"); 158 | debugPrintHeader(""); 159 | } 160 | 161 | static void 162 | disassembleHexColor(const MenuHexColor* color) 163 | { 164 | assert(color); 165 | 166 | debugMsg(true, "| "); 167 | debugMsgWithIndent(0, "| Hex string: '%s'", color->hex); 168 | debugMsgWithIndent(0, "| Red value: %#02X", color->r * 255); 169 | debugMsgWithIndent(0, "| Green value: %#02X", color->g * 255); 170 | debugMsgWithIndent(0, "| Blue value: %#02X", color->b * 255); 171 | debugMsgWithIndent(0, "| Alpha value: %#02X", color->a * 255); 172 | debugMsg(true, "| "); 173 | } 174 | 175 | void 176 | disassembleHexColors(const MenuHexColor* colors) 177 | { 178 | assert(colors); 179 | 180 | for (int i = 0; i < MENU_COLOR_LAST; i++) 181 | { 182 | /* TODO refactor */ 183 | switch (i) 184 | { 185 | case MENU_COLOR_KEY: 186 | { 187 | debugMsg(true, "|----- Foreground Key Color -----"); 188 | break; 189 | } 190 | case MENU_COLOR_DELIMITER: 191 | { 192 | debugMsg(true, "|---- Foreground Delim Color ----"); 193 | break; 194 | } 195 | case MENU_COLOR_PREFIX: 196 | { 197 | debugMsg(true, "|--- Foreground Prefix Color ----"); 198 | break; 199 | } 200 | case MENU_COLOR_CHORD: 201 | { 202 | debugMsg(true, "|--- Foreground Chord Color -----"); 203 | break; 204 | } 205 | case MENU_COLOR_BACKGROUND: 206 | { 207 | debugMsg(true, "|------- Background color -------"); 208 | break; 209 | } 210 | case MENU_COLOR_BORDER: 211 | { 212 | debugMsg(true, "|--------- Border color ---------"); 213 | break; 214 | } 215 | default: errorMsg("| Got unexpected color index: '%d'.", i); return; 216 | } 217 | 218 | disassembleHexColor(&colors[i]); 219 | } 220 | debugMsgWithIndent(0, "|--------------------------------"); 221 | } 222 | 223 | static char 224 | getDelim(int* count, char a, char b) 225 | { 226 | assert(count); 227 | 228 | return ((*count)-- > 1 ? a : b); 229 | } 230 | 231 | static void 232 | disassembleMod(const Modifiers* mods, int indent) 233 | { 234 | assert(mods); 235 | 236 | debugMsgWithIndent(indent, "| Mods: "); 237 | 238 | if (!hasActiveModifier(mods)) 239 | { 240 | printf("NONE\n"); 241 | return; 242 | } 243 | 244 | int count = countModifiers(mods); 245 | if (mods->ctrl) printf("CTRL%c", getDelim(&count, '|', '\n')); 246 | if (mods->alt) printf("ALT%c", getDelim(&count, '|', '\n')); 247 | if (mods->hyper) printf("HYPER%c", getDelim(&count, '|', '\n')); 248 | if (mods->shift) printf("SHIFT%c", getDelim(&count, '|', '\n')); 249 | } 250 | 251 | static bool 252 | disassembleSpecial(SpecialKey special, int indent) 253 | { 254 | debugMsgWithIndent(indent, "| Special: "); 255 | const char* text = getSpecialKeyLiteral(special); 256 | bool flag = special == SPECIAL_KEY_NONE ? false : true; 257 | 258 | printf("%s|%d\n", text, special); 259 | return flag; 260 | } 261 | 262 | static void 263 | debugString(const char* text, const char* value, int indent) 264 | { 265 | assert(text); 266 | 267 | debugMsgWithIndent(indent, "| %-19s'%s'", text, value); 268 | } 269 | 270 | void 271 | disassembleKey(const Key* key) 272 | { 273 | assert(key); 274 | 275 | debugPrintHeader(" Key "); 276 | debugMsgWithIndent(0, "|"); 277 | disassembleKeyWithoutHeader(key, 0); 278 | debugMsgWithIndent(0, "|"); 279 | debugPrintHeader(""); 280 | } 281 | 282 | void 283 | disassembleKeyWithoutHeader(const Key* key, int indent) 284 | { 285 | assert(key); 286 | 287 | disassembleMod(&key->mods, indent); 288 | if (disassembleSpecial(key->special, indent)) 289 | { 290 | debugString("Key:", getSpecialKeyRepr(key->special), indent); 291 | } 292 | else 293 | { 294 | debugString("Key:", key->repr, indent); 295 | } 296 | debugMsgWithIndent(indent, "| Length: %04d", key->len); 297 | } 298 | 299 | void 300 | disassembleFlags(const ChordFlags* flags, int indent) 301 | { 302 | assert(flags); 303 | 304 | debugMsgWithIndent(indent, "| Flags: "); 305 | 306 | if (!hasChordFlags(flags)) 307 | { 308 | printf("WK_FLAG_DEFAULTS\n"); 309 | return; 310 | } 311 | 312 | int count = countChordFlags(flags); 313 | if (flags->keep) printf("KEEP%c", getDelim(&count, '|', '\n')); 314 | if (flags->close) printf("CLOSE%c", getDelim(&count, '|', '\n')); 315 | if (flags->inherit) printf("INHERIT%c", getDelim(&count, '|', '\n')); 316 | if (flags->ignore) printf("IGNORE%c", getDelim(&count, '|', '\n')); 317 | if (flags->unhook) printf("UNHOOK%c", getDelim(&count, '|', '\n')); 318 | if (flags->deflag) printf("DEFLAG%c", getDelim(&count, '|', '\n')); 319 | if (flags->nobefore) printf("NO_BEFORE%c", getDelim(&count, '|', '\n')); 320 | if (flags->noafter) printf("NO_AFTER%c", getDelim(&count, '|', '\n')); 321 | if (flags->write) printf("WRITE%c", getDelim(&count, '|', '\n')); 322 | if (flags->execute) printf("EXECUTE%c", getDelim(&count, '|', '\n')); 323 | if (flags->syncCommand) printf("SYNC_COMMAND%c", getDelim(&count, '|', '\n')); 324 | if (flags->syncBefore) printf("BEFORE_SYNC%c", getDelim(&count, '|', '\n')); 325 | if (flags->syncAfter) printf("AFTER_SYNC%c", getDelim(&count, '|', '\n')); 326 | } 327 | 328 | void 329 | disassembleKeyChord(const KeyChord* keyChord, int indent) 330 | { 331 | assert(keyChord); 332 | 333 | disassembleKeyWithoutHeader(&keyChord->key, indent); 334 | debugMsgWithIndent(indent, "| Description: \"%s\"", keyChord->description); 335 | debugMsgWithIndent(indent, "| Command: %{{ %s }}", keyChord->command); 336 | debugMsgWithIndent(indent, "| Before: %{{ %s }}", keyChord->before); 337 | debugMsgWithIndent(indent, "| After: %{{ %s }}", keyChord->after); 338 | disassembleFlags(&keyChord->flags, indent); 339 | } 340 | 341 | void 342 | disassembleKeyChords(const KeyChord* keyChords, int indent) 343 | { 344 | assert(keyChords); 345 | 346 | if (indent == 0) 347 | { 348 | debugPrintHeaderWithIndent(indent, " KeyChords "); 349 | } 350 | for (uint32_t i = 0; keyChords[i].state == KEY_CHORD_STATE_NOT_NULL; i++) 351 | { 352 | debugMsgWithIndent(indent, "|"); 353 | debugMsgWithIndent(indent, "| Chord Index: %04u", i); 354 | disassembleKeyChord(&keyChords[i], indent); 355 | debugMsgWithIndent(indent, "|"); 356 | if (keyChords[i].keyChords) 357 | { 358 | debugMsgWithIndent( 359 | indent, 360 | "|------------ Nested KeyChords: %04u -------------", 361 | countKeyChords(keyChords[i].keyChords) 362 | ); 363 | disassembleKeyChords(keyChords[i].keyChords, indent + 1); 364 | } 365 | debugPrintHeaderWithIndent(indent, "-"); 366 | } 367 | if (indent == 0) printf("\n"); 368 | } 369 | 370 | void 371 | disassembleKeyChordsShallow(const KeyChord* keyChords, uint32_t len) 372 | { 373 | assert(keyChords); 374 | 375 | for (uint32_t i = 0; i < len; i++) 376 | { 377 | disassembleKeyChordWithHeader(&keyChords[i], 0); 378 | } 379 | } 380 | 381 | void 382 | disassembleKeyChordWithHeader(const KeyChord* keyChord, int indent) 383 | { 384 | assert(keyChord); 385 | 386 | debugPrintHeaderWithIndent(indent, " KeyChord "); 387 | debugMsgWithIndent(indent, "|"); 388 | disassembleKeyChord(keyChord, indent); 389 | debugMsgWithIndent(indent, "|"); 390 | debugPrintHeaderWithIndent(indent, ""); 391 | } 392 | 393 | void 394 | disassembleMenu(const Menu* menu) 395 | { 396 | assert(menu); 397 | 398 | debugPrintHeader(" Menu "); 399 | debugMsg(true, "|"); 400 | debugMsgWithIndent(0, "| Delimiter: '%s'", menu->delimiter); 401 | debugMsgWithIndent(0, "| Max columns: %04u", menu->maxCols); 402 | debugMsgWithIndent(0, "| Menu width: %04i", menu->menuWidth); 403 | debugMsgWithIndent(0, "| Menu gap: %04i", menu->menuGap); 404 | debugMsgWithIndent(0, "| Width padding: %04u", menu->wpadding); 405 | debugMsgWithIndent(0, "| Height padding: %04u", menu->hpadding); 406 | debugMsgWithIndent(0, "| Cell height: %04u", menu->cellHeight); 407 | debugMsgWithIndent(0, "| Rows: %04u", menu->rows); 408 | debugMsgWithIndent(0, "| Cols: %04u", menu->cols); 409 | debugMsgWithIndent(0, "| Width: %04u", menu->width); 410 | debugMsgWithIndent(0, "| Height: %04u", menu->height); 411 | debugMsgWithIndent(0, "| Window position: %s", 412 | (menu->position == MENU_POS_BOTTOM ? "BOTTOM" : "TOP") 413 | ); 414 | debugMsgWithIndent(0, "| Border width: %04u", menu->borderWidth); 415 | debugMsgWithIndent(0, "|"); 416 | disassembleHexColors(menu->colors); 417 | debugMsgWithIndent(0, "|"); 418 | debugMsgWithIndent(0, "| Shell: '%s'", menu->shell); 419 | debugMsgWithIndent(0, "| Font: '%s'", menu->font); 420 | debugMsgWithIndent(0, "| Chords: %p", menu->keyChords); 421 | debugMsgWithIndent(0, "| Chord count: %04u", menu->keyChordCount); 422 | debugMsgWithIndent(0, "| Debug: %s", "true"); 423 | debugMsgWithIndent(0, "| Keys: %s", menu->client.keys); 424 | debugMsgWithIndent(0, "| Transpile: %s", menu->client.transpile); 425 | debugMsgWithIndent(0, "| wks file: '%s'", menu->client.wksFile); 426 | debugMsgWithIndent(0, "| Try script: %s", (menu->client.tryScript ? "true" : "false")); 427 | if (menu->client.script.string) 428 | { 429 | debugMsgWithIndent(0, "| Script:"); 430 | debugMsgWithIndent(0, "|"); 431 | debugTextWithLineNumber(menu->client.script.string); 432 | debugMsgWithIndent(0, "|"); 433 | } 434 | else 435 | { 436 | debugMsgWithIndent(0, "| Script: (null)"); 437 | } 438 | debugMsgWithIndent(0, "| Script capacity: %04zu", menu->client.script.capacity); 439 | debugMsgWithIndent(0, "| Script count: %04zu", menu->client.script.count); 440 | debugMsgWithIndent(0, "|"); 441 | debugPrintHeader(""); 442 | } 443 | 444 | void 445 | disassembleStatus(MenuStatus status) 446 | { 447 | switch (status) 448 | { 449 | case MENU_STATUS_RUNNING: debugMsg(true, "WK_STATUS_RUNNING"); break; 450 | case MENU_STATUS_DAMAGED: debugMsg(true, "WK_STATUS_DAMAGED"); break; 451 | case MENU_STATUS_EXIT_OK: debugMsg(true, "WK_STATUS_EXIT_OK"); break; 452 | case MENU_STATUS_EXIT_SOFTWARE: debugMsg(true, "WK_STATUS_EXIT_SOFTWARE"); break; 453 | default: debugMsg(true, "WK_STATUS_UNKNOWN"); break; 454 | } 455 | } 456 | 457 | -------------------------------------------------------------------------------- /src/common/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMMON_DEBUG_H_ 2 | #define WK_COMMON_DEBUG_H_ 3 | 4 | #include "menu.h" 5 | #include "key_chord.h" 6 | 7 | void debugMsg(bool debug, const char* fmt, ...); 8 | void debugMsgWithIndent(int indent, const char* fmt, ...); 9 | void debugPrintHeader(const char* header); 10 | void debugPrintHeaderWithIndent(int indent, const char* header); 11 | void debugTextWithLineNumber(const char* text); 12 | void debugTextLenWithLineNumber(const char* text, size_t len); 13 | void disassembleFlags(const ChordFlags* flags, int indent); 14 | void disassembleGrid(uint32_t x, uint32_t y, uint32_t r, uint32_t c, uint32_t wp, uint32_t hp, uint32_t cw, uint32_t ch, uint32_t count); 15 | void disassembleHexColors(const MenuHexColor* colors); 16 | void disassembleKey(const Key* key); 17 | void disassembleKeyWithoutHeader(const Key* key, int indent); 18 | void disassembleKeyChord(const KeyChord* keyChord, int indent); 19 | void disassembleKeyChords(const KeyChord* keyChords, int indent); 20 | void disassembleKeyChordsShallow(const KeyChord* keyChords, uint32_t len); 21 | void disassembleKeyChordWithHeader(const KeyChord* keyChord, int indent); 22 | void disassembleMenu(const Menu* menu); 23 | void disassembleStatus(MenuStatus status); 24 | 25 | #endif /* WK_COMMON_DEBUG_H_ */ 26 | -------------------------------------------------------------------------------- /src/common/key_chord.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* local includes */ 8 | #include "key_chord.h" 9 | 10 | typedef struct 11 | { 12 | const char* literal; 13 | const char* repr; 14 | } SpecialTable; 15 | 16 | static const SpecialTable specialTable[SPECIAL_KEY_LAST] = { 17 | [SPECIAL_KEY_NONE] = { "SPECIAL_KEY_NONE", "None" }, 18 | [SPECIAL_KEY_LEFT] = { "SPECIAL_KEY_LEFT", "Left" }, 19 | [SPECIAL_KEY_RIGHT] = { "SPECIAL_KEY_RIGHT", "Right" }, 20 | [SPECIAL_KEY_UP] = { "SPECIAL_KEY_UP", "Up" }, 21 | [SPECIAL_KEY_DOWN] = { "SPECIAL_KEY_DOWN", "Down" }, 22 | [SPECIAL_KEY_TAB] = { "SPECIAL_KEY_TAB", "TAB" }, 23 | [SPECIAL_KEY_SPACE] = { "SPECIAL_KEY_SPACE", "SPC" }, 24 | [SPECIAL_KEY_RETURN] = { "SPECIAL_KEY_RETURN", "RET" }, 25 | [SPECIAL_KEY_DELETE] = { "SPECIAL_KEY_DELETE", "DEL" }, 26 | [SPECIAL_KEY_ESCAPE] = { "SPECIAL_KEY_ESCAPE", "ESC" }, 27 | [SPECIAL_KEY_HOME] = { "SPECIAL_KEY_HOME", "Home" }, 28 | [SPECIAL_KEY_PAGE_UP] = { "SPECIAL_KEY_PAGE_UP", "PgUp" }, 29 | [SPECIAL_KEY_PAGE_DOWN] = { "SPECIAL_KEY_PAGE_DOWN", "PgDown" }, 30 | [SPECIAL_KEY_END] = { "SPECIAL_KEY_END", "End" }, 31 | [SPECIAL_KEY_BEGIN] = { "SPECIAL_KEY_BEGIN", "Begin" }, 32 | [SPECIAL_KEY_F1] = { "SPECIAL_KEY_F1", "F1" }, 33 | [SPECIAL_KEY_F2] = { "SPECIAL_KEY_F2", "F2" }, 34 | [SPECIAL_KEY_F3] = { "SPECIAL_KEY_F3", "F3" }, 35 | [SPECIAL_KEY_F4] = { "SPECIAL_KEY_F4", "F4" }, 36 | [SPECIAL_KEY_F5] = { "SPECIAL_KEY_F5", "F5" }, 37 | [SPECIAL_KEY_F6] = { "SPECIAL_KEY_F6", "F6" }, 38 | [SPECIAL_KEY_F7] = { "SPECIAL_KEY_F7", "F7" }, 39 | [SPECIAL_KEY_F8] = { "SPECIAL_KEY_F8", "F8" }, 40 | [SPECIAL_KEY_F9] = { "SPECIAL_KEY_F9", "F9" }, 41 | [SPECIAL_KEY_F10] = { "SPECIAL_KEY_F10", "F10" }, 42 | [SPECIAL_KEY_F11] = { "SPECIAL_KEY_F11", "F11" }, 43 | [SPECIAL_KEY_F12] = { "SPECIAL_KEY_F12", "F12" }, 44 | [SPECIAL_KEY_F13] = { "SPECIAL_KEY_F13", "F13" }, 45 | [SPECIAL_KEY_F14] = { "SPECIAL_KEY_F14", "F14" }, 46 | [SPECIAL_KEY_F15] = { "SPECIAL_KEY_F15", "F15" }, 47 | [SPECIAL_KEY_F16] = { "SPECIAL_KEY_F16", "F16" }, 48 | [SPECIAL_KEY_F17] = { "SPECIAL_KEY_F17", "F17" }, 49 | [SPECIAL_KEY_F18] = { "SPECIAL_KEY_F18", "F18" }, 50 | [SPECIAL_KEY_F19] = { "SPECIAL_KEY_F19", "F19" }, 51 | [SPECIAL_KEY_F20] = { "SPECIAL_KEY_F20", "F20" }, 52 | [SPECIAL_KEY_F21] = { "SPECIAL_KEY_F21", "F21" }, 53 | [SPECIAL_KEY_F22] = { "SPECIAL_KEY_F22", "F22" }, 54 | [SPECIAL_KEY_F23] = { "SPECIAL_KEY_F23", "F23" }, 55 | [SPECIAL_KEY_F24] = { "SPECIAL_KEY_F24", "F24" }, 56 | [SPECIAL_KEY_F25] = { "SPECIAL_KEY_F25", "F25" }, 57 | [SPECIAL_KEY_F26] = { "SPECIAL_KEY_F26", "F26" }, 58 | [SPECIAL_KEY_F27] = { "SPECIAL_KEY_F27", "F27" }, 59 | [SPECIAL_KEY_F28] = { "SPECIAL_KEY_F28", "F28" }, 60 | [SPECIAL_KEY_F29] = { "SPECIAL_KEY_F29", "F29" }, 61 | [SPECIAL_KEY_F30] = { "SPECIAL_KEY_F30", "F30" }, 62 | [SPECIAL_KEY_F31] = { "SPECIAL_KEY_F31", "F31" }, 63 | [SPECIAL_KEY_F32] = { "SPECIAL_KEY_F32", "F32" }, 64 | [SPECIAL_KEY_F33] = { "SPECIAL_KEY_F33", "F33" }, 65 | [SPECIAL_KEY_F34] = { "SPECIAL_KEY_F34", "F34" }, 66 | [SPECIAL_KEY_F35] = { "SPECIAL_KEY_F35", "F35" }, 67 | [SPECIAL_KEY_AUDIO_VOL_DOWN] = { "SPECIAL_KEY_AUDIO_VOL_DOWN", "VolDown" }, 68 | [SPECIAL_KEY_AUDIO_VOL_MUTE] = { "SPECIAL_KEY_AUDIO_VOL_MUTE", "VolMute" }, 69 | [SPECIAL_KEY_AUDIO_VOL_UP] = { "SPECIAL_KEY_AUDIO_VOL_UP", "VolUp" }, 70 | [SPECIAL_KEY_AUDIO_PLAY] = { "SPECIAL_KEY_AUDIO_PLAY", "Play" }, 71 | [SPECIAL_KEY_AUDIO_STOP] = { "SPECIAL_KEY_AUDIO_STOP", "Stop" }, 72 | [SPECIAL_KEY_AUDIO_PREV] = { "SPECIAL_KEY_AUDIO_PREV", "Prev" }, 73 | [SPECIAL_KEY_AUDIO_NEXT] = { "SPECIAL_KEY_AUDIO_NEXT", "Next" }, 74 | }; 75 | 76 | void 77 | copyChordFlags(const ChordFlags* from, ChordFlags* to) 78 | { 79 | assert(from), assert(to); 80 | 81 | to->keep = from->keep; 82 | to->close = from->close; 83 | to->inherit = from->inherit; 84 | to->ignore = from->ignore; 85 | to->ignoreSort = from->ignoreSort; 86 | to->unhook = from->unhook; 87 | to->deflag = from->deflag; 88 | to->nobefore = from->nobefore; 89 | to->noafter = from->noafter; 90 | to->write = from->write; 91 | to->execute = from->execute; 92 | to->syncCommand = from->syncCommand; 93 | to->syncBefore = from->syncBefore; 94 | to->syncAfter = from->syncAfter; 95 | } 96 | 97 | void 98 | copyChordModifiers(const Modifiers* from, Modifiers* to) 99 | { 100 | assert(from), assert(to); 101 | 102 | to->ctrl = from->ctrl; 103 | to->alt = from->alt; 104 | to->hyper = from->hyper; 105 | to->shift = from->shift; 106 | } 107 | 108 | void 109 | copyKey(const Key* from, Key* to) 110 | { 111 | assert(from), assert(to); 112 | 113 | copyChordModifiers(&from->mods, &to->mods); 114 | to->special = from->special; 115 | to->repr = from->repr; 116 | to->len = from->len; 117 | } 118 | 119 | void 120 | copyKeyChord(const KeyChord* from, KeyChord* to) 121 | { 122 | assert(from), assert(to); 123 | 124 | to->state = from->state; 125 | copyKey(&from->key, &to->key); 126 | to->description = from->description; 127 | to->command = from->command; 128 | to->before = from->before; 129 | to->after = from->after; 130 | copyChordFlags(&from->flags, &to->flags); 131 | to->keyChords = from->keyChords; 132 | } 133 | 134 | uint32_t 135 | countChordFlags(const ChordFlags* flags) 136 | { 137 | assert(flags); 138 | 139 | uint32_t result = 0; 140 | 141 | if (flags->keep) result++; 142 | if (flags->close) result++; 143 | if (flags->inherit) result++; 144 | if (flags->ignore) result++; 145 | if (flags->ignoreSort) result++; 146 | if (flags->unhook) result++; 147 | if (flags->deflag) result++; 148 | if (flags->nobefore) result++; 149 | if (flags->noafter) result++; 150 | if (flags->write) result++; 151 | if (flags->execute) result++; 152 | if (flags->syncCommand) result++; 153 | if (flags->syncBefore) result++; 154 | if (flags->syncAfter) result++; 155 | 156 | return result; 157 | } 158 | 159 | uint32_t 160 | countKeyChords(const KeyChord* keyChords) 161 | { 162 | assert(keyChords); 163 | 164 | uint32_t count = 0; 165 | while (keyChords[count].state == KEY_CHORD_STATE_NOT_NULL) count++; 166 | 167 | return count; 168 | } 169 | 170 | uint32_t 171 | countModifiers(const Modifiers* mods) 172 | { 173 | assert(mods); 174 | 175 | uint32_t result = 0; 176 | if (mods->ctrl) result++; 177 | if (mods->alt) result++; 178 | if (mods->hyper) result++; 179 | if (mods->shift) result++; 180 | 181 | return result; 182 | } 183 | 184 | const char* 185 | getSpecialKeyLiteral(const SpecialKey special) 186 | { 187 | return specialTable[special].literal; 188 | } 189 | 190 | const char* 191 | getSpecialKeyRepr(const SpecialKey special) 192 | { 193 | return specialTable[special].repr; 194 | } 195 | 196 | bool 197 | hasChordFlags(const ChordFlags* flags) 198 | { 199 | assert(flags); 200 | 201 | return !hasDefaultChordFlags(flags); 202 | } 203 | 204 | void 205 | initKey(Key* key) 206 | { 207 | assert(key); 208 | 209 | initChordModifiers(&key->mods); 210 | key->special = SPECIAL_KEY_NONE; 211 | key->repr = NULL; 212 | key->len = 0; 213 | } 214 | 215 | void 216 | initKeyChord(KeyChord* keyChord) 217 | { 218 | assert(keyChord); 219 | 220 | keyChord->state = KEY_CHORD_STATE_NOT_NULL; 221 | initKey(&keyChord->key); 222 | keyChord->description = NULL; 223 | keyChord->command = NULL; 224 | keyChord->before = NULL; 225 | keyChord->after = NULL; 226 | initChordFlags(&keyChord->flags); 227 | keyChord->keyChords = NULL; 228 | } 229 | 230 | void 231 | initChordFlags(ChordFlags* flags) 232 | { 233 | assert(flags); 234 | 235 | flags->keep = false; 236 | flags->close = false; 237 | flags->inherit = false; 238 | flags->ignore = false; 239 | flags->ignoreSort = false; 240 | flags->unhook = false; 241 | flags->deflag = false; 242 | flags->nobefore = false; 243 | flags->noafter = false; 244 | flags->write = false; 245 | flags->execute = false; 246 | flags->syncCommand = false; 247 | flags->syncBefore = false; 248 | flags->syncAfter = false; 249 | } 250 | 251 | void 252 | initChordModifiers(Modifiers* mods) 253 | { 254 | assert(mods); 255 | 256 | mods->ctrl = false; 257 | mods->alt = false; 258 | mods->hyper = false; 259 | mods->shift = false; 260 | } 261 | 262 | bool 263 | hasActiveModifier(const Modifiers* mods) 264 | { 265 | assert(mods); 266 | 267 | return (mods->ctrl || mods->alt || mods->hyper || mods->shift); 268 | } 269 | 270 | bool 271 | hasDefaultChordFlags(const ChordFlags* flags) 272 | { 273 | return !( 274 | flags->keep || 275 | flags->close || 276 | flags->inherit || 277 | flags->ignore || 278 | flags->ignoreSort || 279 | flags->unhook || 280 | flags->deflag || 281 | flags->nobefore || 282 | flags->noafter || 283 | flags->write || 284 | flags->execute || 285 | flags->syncCommand || 286 | flags->syncBefore || 287 | flags->syncAfter 288 | ); 289 | } 290 | 291 | static bool 292 | modsAreEqual(const Modifiers* a, const Modifiers* b, bool shiftIsSignificant) 293 | { 294 | assert(a), assert(b); 295 | 296 | return ( 297 | a->ctrl == b->ctrl && 298 | a->alt == b->alt && 299 | a->hyper == b->hyper && 300 | (!shiftIsSignificant || a->shift == b->shift) 301 | ); 302 | } 303 | 304 | static bool 305 | keysAreSpecial(const Key* a, const Key* b) 306 | { 307 | assert(a), assert(b); 308 | 309 | return a->special != SPECIAL_KEY_NONE && b->special != SPECIAL_KEY_NONE; 310 | } 311 | 312 | static bool 313 | specialKeysAreEqual(const Key* a, const Key* b) 314 | { 315 | assert(a), assert(b); 316 | 317 | return ( 318 | a->special == b->special && 319 | modsAreEqual(&a->mods, &b->mods, true) 320 | ); 321 | } 322 | 323 | bool 324 | keysAreEqual(const Key* a, const Key* b, bool shiftIsSignificant) 325 | { 326 | assert(a), assert(b); 327 | 328 | if (keysAreSpecial(a, b) && specialKeysAreEqual(a, b)) return true; 329 | return ( 330 | a->special == b->special && 331 | modsAreEqual(&a->mods, &b->mods, shiftIsSignificant) && 332 | strcmp(a->repr, b->repr) == 0 333 | ); 334 | } 335 | 336 | void 337 | makeNullKeyChord(KeyChord* keyChord) 338 | { 339 | assert(keyChord); 340 | 341 | keyChord->state = KEY_CHORD_STATE_IS_NULL; 342 | initKey(&keyChord->key); 343 | keyChord->description = NULL; 344 | keyChord->command = NULL; 345 | keyChord->before = NULL; 346 | keyChord->after = NULL; 347 | initChordFlags(&keyChord->flags); 348 | keyChord->keyChords = NULL; 349 | } 350 | -------------------------------------------------------------------------------- /src/common/key_chord.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMMON_KEY_CHORD_H_ 2 | #define WK_COMMON_KEY_CHORD_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum 9 | { 10 | KEY_CHORD_STATE_IS_NULL, 11 | KEY_CHORD_STATE_NOT_NULL, 12 | } KeyChordState; 13 | 14 | typedef enum 15 | { 16 | KEY_TYPE_IS_STRICTLY_MOD, 17 | KEY_TYPE_IS_SPECIAL, 18 | KEY_TYPE_IS_NORMAL, 19 | KEY_TYPE_IS_UNKNOWN, 20 | } KeyType; 21 | 22 | typedef struct 23 | { 24 | bool ctrl:1; 25 | bool alt:1; 26 | bool hyper:1; 27 | bool shift:1; 28 | } Modifiers; 29 | 30 | typedef enum 31 | { 32 | SPECIAL_KEY_NONE, 33 | SPECIAL_KEY_LEFT, 34 | SPECIAL_KEY_RIGHT, 35 | SPECIAL_KEY_UP, 36 | SPECIAL_KEY_DOWN, 37 | SPECIAL_KEY_TAB, 38 | SPECIAL_KEY_SPACE, 39 | SPECIAL_KEY_RETURN, 40 | SPECIAL_KEY_DELETE, 41 | SPECIAL_KEY_ESCAPE, 42 | SPECIAL_KEY_HOME, 43 | SPECIAL_KEY_PAGE_UP, 44 | SPECIAL_KEY_PAGE_DOWN, 45 | SPECIAL_KEY_END, 46 | SPECIAL_KEY_BEGIN, 47 | SPECIAL_KEY_F1, 48 | SPECIAL_KEY_F2, 49 | SPECIAL_KEY_F3, 50 | SPECIAL_KEY_F4, 51 | SPECIAL_KEY_F5, 52 | SPECIAL_KEY_F6, 53 | SPECIAL_KEY_F7, 54 | SPECIAL_KEY_F8, 55 | SPECIAL_KEY_F9, 56 | SPECIAL_KEY_F10, 57 | SPECIAL_KEY_F11, 58 | SPECIAL_KEY_F12, 59 | SPECIAL_KEY_F13, 60 | SPECIAL_KEY_F14, 61 | SPECIAL_KEY_F15, 62 | SPECIAL_KEY_F16, 63 | SPECIAL_KEY_F17, 64 | SPECIAL_KEY_F18, 65 | SPECIAL_KEY_F19, 66 | SPECIAL_KEY_F20, 67 | SPECIAL_KEY_F21, 68 | SPECIAL_KEY_F22, 69 | SPECIAL_KEY_F23, 70 | SPECIAL_KEY_F24, 71 | SPECIAL_KEY_F25, 72 | SPECIAL_KEY_F26, 73 | SPECIAL_KEY_F27, 74 | SPECIAL_KEY_F28, 75 | SPECIAL_KEY_F29, 76 | SPECIAL_KEY_F30, 77 | SPECIAL_KEY_F31, 78 | SPECIAL_KEY_F32, 79 | SPECIAL_KEY_F33, 80 | SPECIAL_KEY_F34, 81 | SPECIAL_KEY_F35, 82 | SPECIAL_KEY_AUDIO_VOL_DOWN, 83 | SPECIAL_KEY_AUDIO_VOL_MUTE, 84 | SPECIAL_KEY_AUDIO_VOL_UP, 85 | SPECIAL_KEY_AUDIO_PLAY, 86 | SPECIAL_KEY_AUDIO_STOP, 87 | SPECIAL_KEY_AUDIO_PREV, 88 | SPECIAL_KEY_AUDIO_NEXT, 89 | SPECIAL_KEY_LAST, 90 | } SpecialKey; 91 | 92 | typedef struct 93 | { 94 | bool keep:1; 95 | bool close:1; 96 | bool inherit:1; 97 | bool ignore:1; 98 | bool ignoreSort:1; 99 | bool unhook:1; 100 | bool deflag:1; 101 | bool nobefore:1; 102 | bool noafter:1; 103 | bool write:1; 104 | bool execute:1; 105 | bool syncCommand:1; 106 | bool syncBefore:1; 107 | bool syncAfter:1; 108 | } ChordFlags; 109 | 110 | typedef struct 111 | { 112 | Modifiers mods; 113 | SpecialKey special; 114 | char* repr; 115 | size_t len; 116 | } Key; 117 | 118 | typedef struct KeyChord 119 | { 120 | KeyChordState state; 121 | Key key; 122 | char* description; 123 | char* command; 124 | char* before; 125 | char* after; 126 | ChordFlags flags; 127 | struct KeyChord* keyChords; 128 | } KeyChord; 129 | 130 | void copyChordFlags(const ChordFlags* from, ChordFlags* to); 131 | void copyChordModifiers(const Modifiers* from, Modifiers* to); 132 | void copyKey(const Key* from, Key* to); 133 | void copyKeyChord(const KeyChord* from, KeyChord* to); 134 | uint32_t countChordFlags(const ChordFlags* flags); 135 | uint32_t countKeyChords(const KeyChord* keyChords); 136 | uint32_t countModifiers(const Modifiers* mods); 137 | const char* getSpecialKeyLiteral(const SpecialKey special); 138 | const char* getSpecialKeyRepr(const SpecialKey special); 139 | bool hasChordFlags(const ChordFlags* flags); 140 | void initKey(Key* key); 141 | void initKeyChord(KeyChord* keyChord); 142 | void initChordFlags(ChordFlags* flags); 143 | void initChordModifiers(Modifiers* mods); 144 | bool hasActiveModifier(const Modifiers* mods); 145 | bool hasDefaultChordFlags(const ChordFlags* flags); 146 | bool keysAreEqual(const Key* a, const Key* b, bool shiftIsSignificant); 147 | void makeNullKeyChord(KeyChord* keyChord); 148 | 149 | #endif /* WK_COMMON_KEY_CHORD_H_ */ 150 | -------------------------------------------------------------------------------- /src/common/memory.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "memory.h" 6 | 7 | void* 8 | reallocate(void* pointer, size_t oldSize, size_t newSize) 9 | { 10 | if (newSize == 0) 11 | { 12 | free(pointer); 13 | return NULL; 14 | } 15 | 16 | void* result = realloc(pointer, newSize); 17 | if (result == NULL) 18 | { 19 | perror("memory allocation"); 20 | exit(EX_UNAVAILABLE); 21 | } 22 | 23 | return result; 24 | } 25 | -------------------------------------------------------------------------------- /src/common/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMMON_MEMORY_H_ 2 | #define WK_COMMON_MEMORY_H_ 3 | 4 | #include 5 | 6 | #define ALLOCATE(type, count) \ 7 | (type*)reallocate(NULL, 0, sizeof(type) * (count)) 8 | #define FREE(type, pointer) reallocate(pointer, sizeof(type), 0) 9 | #define GROW_CAPACITY(capacity) \ 10 | ((capacity) < 8 ? 8 : (capacity) * 2) 11 | #define GROW_ARRAY(type, pointer, oldCount, newCount) \ 12 | (type*)reallocate( \ 13 | pointer, sizeof(type) * (oldCount), sizeof(type) * (newCount) \ 14 | ) 15 | #define FREE_ARRAY(type, pointer, oldCount) \ 16 | reallocate(pointer, sizeof(type) * (oldCount), 0) 17 | 18 | void* reallocate(void* pointer, size_t oldSize, size_t newSize); 19 | 20 | #endif /* WK_COMMON_MEMORY_H_ */ 21 | -------------------------------------------------------------------------------- /src/common/menu.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMMON_MENU_H_ 2 | #define WK_COMMON_MENU_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include "string.h" 8 | #include "key_chord.h" 9 | 10 | #define MENU_MIN_WIDTH 80 11 | 12 | typedef void (*CleanupFP)(void* xp); 13 | 14 | typedef enum 15 | { 16 | FOREGROUND_COLOR_KEY, 17 | FOREGROUND_COLOR_DELIMITER, 18 | FOREGROUND_COLOR_PREFIX, 19 | FOREGROUND_COLOR_CHORD, 20 | FOREGROUND_COLOR_LAST, 21 | } ForegroundColor; 22 | 23 | typedef enum 24 | { 25 | MENU_COLOR_KEY, 26 | MENU_COLOR_DELIMITER, 27 | MENU_COLOR_PREFIX, 28 | MENU_COLOR_CHORD, 29 | MENU_COLOR_BACKGROUND, 30 | MENU_COLOR_BORDER, 31 | MENU_COLOR_LAST 32 | } MenuColor; 33 | 34 | typedef struct 35 | { 36 | const char* hex; 37 | uint8_t r; 38 | uint8_t g; 39 | uint8_t b; 40 | uint8_t a; 41 | } MenuHexColor; 42 | 43 | typedef enum 44 | { 45 | MENU_STATUS_RUNNING, 46 | MENU_STATUS_DAMAGED, 47 | MENU_STATUS_EXIT_OK, 48 | MENU_STATUS_EXIT_SOFTWARE, 49 | } MenuStatus; 50 | 51 | typedef enum 52 | { 53 | MENU_POS_BOTTOM, 54 | MENU_POS_TOP 55 | } MenuPosition; 56 | 57 | typedef struct 58 | { 59 | const char* delimiter; 60 | uint32_t maxCols; 61 | int32_t menuWidth; 62 | int32_t menuGap; 63 | uint32_t wpadding; 64 | uint32_t hpadding; 65 | uint32_t cellHeight; 66 | uint32_t rows; 67 | uint32_t cols; 68 | uint32_t width; 69 | uint32_t height; 70 | MenuPosition position; 71 | uint32_t borderWidth; 72 | double borderRadius; 73 | MenuHexColor colors[MENU_COLOR_LAST]; 74 | const char* shell; 75 | const char* font; 76 | const char* implicitArrayKeys; 77 | KeyChord* keyChords; 78 | KeyChord* keyChordsHead; 79 | uint32_t keyChordCount; 80 | bool debug; 81 | bool sort; 82 | bool dirty; 83 | struct Client 84 | { 85 | const char* keys; 86 | const char* transpile; 87 | const char* wksFile; 88 | bool tryScript; 89 | String script; 90 | } client; 91 | struct Garbage 92 | { 93 | char* shell; 94 | char* font; 95 | char* implicitArrayKeys; 96 | char* foregroundKeyColor; 97 | char* foregroundDelimiterColor; 98 | char* foregroundPrefixColor; 99 | char* foregroundChordColor; 100 | char* backgroundColor; 101 | char* borderColor; 102 | } garbage; 103 | uint32_t delay; 104 | struct timespec timer; 105 | CleanupFP cleanupfp; 106 | void* xp; 107 | } Menu; 108 | 109 | void countMenuKeyChords(Menu* menu); 110 | int displayMenu(Menu* menu); 111 | void freeMenuGarbage(Menu* menu); 112 | MenuStatus handleKeypress(Menu* menu, const Key* key, bool shiftIsSignificant); 113 | void initMenu(Menu* menu, KeyChord* keyChords); 114 | bool menuIsDelayed(Menu* menu); 115 | void menuResetTimer(Menu* menu); 116 | void parseArgs(Menu* menu, int* argc, char*** argv); 117 | void setMenuColor(Menu* menu, const char* color, MenuColor colorType); 118 | MenuStatus spawn(const Menu* menu, const char* cmd, bool async); 119 | bool statusIsError(MenuStatus status); 120 | bool statusIsRunning(MenuStatus status); 121 | bool tryStdin(Menu* menu); 122 | 123 | #endif /* WK_COMMON_MENU_H_ */ 124 | -------------------------------------------------------------------------------- /src/common/string.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* local includes */ 8 | #include "string.h" 9 | #include "memory.h" 10 | 11 | typedef int (*ConversionFp)(int); 12 | 13 | void 14 | appendInt32ToString(String* dest, int32_t i) 15 | { 16 | assert(dest); 17 | 18 | size_t len = snprintf(NULL, 0, "%d", i); 19 | char buffer[len + 1]; /* +1 for null byte. */ 20 | snprintf(buffer, len + 1, "%d", i); 21 | appendToString(dest, buffer, len); 22 | } 23 | 24 | void 25 | appendUInt32ToString(String* dest, uint32_t i) 26 | { 27 | assert(dest); 28 | 29 | size_t len = snprintf(NULL, 0, "%u", i); 30 | char buffer[len + 1]; /* +1 for null byte. */ 31 | snprintf(buffer, len + 1, "%u", i); 32 | appendToString(dest, buffer, len); 33 | } 34 | 35 | void 36 | appendCharToString(String* dest, char c) 37 | { 38 | assert(dest); 39 | 40 | char buffer[] = { c, '\0' }; 41 | appendToString(dest, buffer, 1); 42 | } 43 | 44 | void 45 | appendEscStringToString(String* dest, const char* source, size_t len) 46 | { 47 | assert(dest), assert(source); 48 | 49 | for (size_t i = 0; i < len; i++) 50 | { 51 | size_t start = i; 52 | while (i < len && source[i] != '\\') i++; 53 | appendToString(dest, source + start, i - start); 54 | } 55 | } 56 | 57 | void 58 | appendToString(String *dest, const char* source, size_t len) 59 | { 60 | assert(dest), assert(source); 61 | if (len < 1) return; 62 | 63 | while (dest->count + len + 1 > dest->capacity) 64 | { 65 | size_t oldCapacity = dest->capacity; 66 | dest->capacity = GROW_CAPACITY(oldCapacity); 67 | dest->string = GROW_ARRAY( 68 | char, dest->string, oldCapacity, dest->capacity 69 | ); 70 | } 71 | 72 | memcpy(dest->string + dest->count, source, len); 73 | dest->count += len; 74 | dest->string[dest->count] = '\0'; 75 | } 76 | 77 | static void 78 | appendCharsToStringWithFunc(String* dest, const char* source, size_t len, ConversionFp fp) 79 | { 80 | assert(dest), assert(source); 81 | if (len < 1) return; 82 | 83 | for (size_t i = 0; i < len; i++) 84 | { 85 | appendCharToString(dest, fp(source[i])); 86 | } 87 | } 88 | 89 | void 90 | appendToStringWithState(String* dest, const char* source, size_t len, StringAppendState state) 91 | { 92 | assert(dest), assert(source); 93 | if (len < 1) return; 94 | 95 | switch (state) 96 | { 97 | case STRING_APPEND_UPPER_FIRST: 98 | { 99 | appendCharToString(dest, toupper(*source)); 100 | appendToString(dest, source + 1, len - 1); 101 | break; 102 | } 103 | case STRING_APPEND_LOWER_FIRST: 104 | { 105 | appendCharToString(dest, tolower(*source)); 106 | appendToString(dest, source + 1, len - 1); 107 | break; 108 | } 109 | case STRING_APPEND_UPPER_ALL: 110 | { 111 | appendCharToString(dest, toupper(*source)); 112 | appendCharsToStringWithFunc(dest, source + 1, len - 1, toupper); 113 | break; 114 | } 115 | case STRING_APPEND_LOWER_ALL: 116 | { 117 | appendCharToString(dest, tolower(*source)); 118 | appendCharsToStringWithFunc(dest, source + 1, len - 1, tolower); 119 | break; 120 | } 121 | default: break; 122 | } 123 | } 124 | 125 | void 126 | disownString(String* string) 127 | { 128 | assert(string); 129 | 130 | string->string = NULL; 131 | string->count = 0; 132 | string->capacity = 0; 133 | } 134 | 135 | void 136 | freeString(String* string) 137 | { 138 | assert(string); 139 | 140 | FREE_ARRAY(char, string->string, string->count); 141 | string->string = NULL; 142 | string->count = 0; 143 | string->capacity = 0; 144 | } 145 | 146 | void 147 | initFromCharString(String* string, char* source) 148 | { 149 | assert(string); 150 | 151 | size_t len = strlen(source); 152 | string->string = source; 153 | string->count = len; 154 | string->capacity = len; 155 | } 156 | 157 | void 158 | initString(String *string) 159 | { 160 | assert(string); 161 | 162 | string->string = NULL; 163 | string->count = 0; 164 | string->capacity = 0; 165 | } 166 | 167 | void 168 | rtrimString(String* string) 169 | { 170 | assert(string); 171 | if (string->count == 0) return; 172 | 173 | while (string->count > 0 && isspace(string->string[string->count - 1])) 174 | { 175 | string->count--; 176 | } 177 | 178 | string->string[string->count] = '\0'; 179 | } 180 | -------------------------------------------------------------------------------- /src/common/string.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMMON_STRING_H_ 2 | #define WK_COMMON_STRING_H_ 3 | 4 | #include 5 | #include 6 | 7 | typedef struct 8 | { 9 | char* string; 10 | size_t count; 11 | size_t capacity; 12 | } String; 13 | 14 | typedef enum 15 | { 16 | STRING_APPEND_UPPER_FIRST, 17 | STRING_APPEND_LOWER_FIRST, 18 | STRING_APPEND_UPPER_ALL, 19 | STRING_APPEND_LOWER_ALL, 20 | } StringAppendState; 21 | 22 | void appendInt32ToString(String* dest, int32_t i); 23 | void appendUInt32ToString(String* dest, uint32_t i); 24 | void appendCharToString(String* dest, char c); 25 | void appendEscStringToString(String* dest, const char* source, size_t len); 26 | void appendToString(String* dest, const char* source, size_t len); 27 | void appendToStringWithState(String* dest, const char* source, size_t len, StringAppendState state); 28 | void disownString(String* string); 29 | void freeString(String* string); 30 | void initFromCharString(String* string, char* source); 31 | void initString(String* string); 32 | void rtrimString(String* string); 33 | 34 | #endif /* WK_COMMON_STRING_H_ */ 35 | -------------------------------------------------------------------------------- /src/compiler/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /* common includes */ 9 | #include "common/common.h" 10 | #include "common/debug.h" 11 | #include "common/key_chord.h" 12 | 13 | /* local includes */ 14 | #include "common.h" 15 | #include "scanner.h" 16 | #include "token.h" 17 | 18 | static bool 19 | addMod(Key* key, TokenType type) 20 | { 21 | assert(key); 22 | 23 | switch (type) 24 | { 25 | case TOKEN_MOD_CTRL: key->mods.ctrl = true; break; 26 | case TOKEN_MOD_ALT: key->mods.alt = true; break; 27 | case TOKEN_MOD_HYPER: key->mods.hyper = true; break; 28 | case TOKEN_MOD_SHIFT: key->mods.shift = true; break; 29 | default: return false; 30 | } 31 | 32 | return true; 33 | } 34 | 35 | static MenuStatus 36 | pressKey(Menu* menu, Scanner* scanner) 37 | { 38 | assert(menu), assert(scanner); 39 | 40 | static const size_t bufmax = 128; 41 | char buffer[bufmax]; 42 | memset(buffer, 0, 32); 43 | Key key = {0}; 44 | Token token = {0}; 45 | initToken(&token); 46 | scanTokenForCompiler(scanner, &token); 47 | 48 | while (addMod(&key, token.type)) 49 | { 50 | scanTokenForCompiler(scanner, &token); 51 | } 52 | 53 | if (token.type == TOKEN_KEY) 54 | { 55 | key.special = SPECIAL_KEY_NONE; 56 | if (token.length > bufmax) 57 | { 58 | errorMsg("Key is longer than max size of %zu: %04zu", bufmax, token.length); 59 | return MENU_STATUS_EXIT_SOFTWARE; 60 | } 61 | memcpy(buffer, token.start, token.length); 62 | buffer[token.length] = '\0'; 63 | key.repr = buffer; 64 | key.len = token.length; 65 | } 66 | else if (token.type == TOKEN_SPECIAL_KEY) 67 | { 68 | key.repr = NULL; 69 | key.special = scanner->special; 70 | } 71 | else 72 | { 73 | errorMsg( 74 | "Key does not appear to be a regular key or a special key: '%.*s'.", 75 | (int)token.length, token.start 76 | ); 77 | return MENU_STATUS_EXIT_SOFTWARE; 78 | } 79 | 80 | if (menu->debug) 81 | { 82 | debugMsg(menu->debug, "Trying to press key: '%.*s'.", (int)token.length, token.start); 83 | disassembleKey(&key); 84 | } 85 | 86 | MenuStatus status = handleKeypress(menu, &key, true); 87 | 88 | if (status == MENU_STATUS_EXIT_SOFTWARE) 89 | { 90 | errorMsg("Could not press key: '%.*s'.", (int)token.length, token.start); 91 | return status; 92 | } 93 | 94 | if (status == MENU_STATUS_EXIT_OK) 95 | { 96 | scanTokenForCompiler(scanner, &token); 97 | if (token.type == TOKEN_EOF) return status; 98 | return MENU_STATUS_EXIT_SOFTWARE; 99 | } 100 | 101 | return status; 102 | } 103 | 104 | MenuStatus 105 | pressKeys(Menu* menu, const char* keys) 106 | { 107 | assert(menu), assert(keys); 108 | 109 | Scanner scanner; 110 | initScanner(&scanner, keys, "KEYS"); 111 | MenuStatus status = pressKey(menu, &scanner); 112 | 113 | while (!isAtEnd(&scanner) && statusIsRunning(status)) 114 | { 115 | status = pressKey(menu, &scanner); 116 | } 117 | 118 | if (status == MENU_STATUS_EXIT_OK && *scanner.current != '\0') 119 | { 120 | errorMsg( 121 | "Reached end of chords but not end of keys: '%s'.", 122 | (scanner.current == scanner.start) ? scanner.start : scanner.current - 1 123 | ); 124 | return status; 125 | } 126 | 127 | return status; 128 | } 129 | -------------------------------------------------------------------------------- /src/compiler/common.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_COMMON_H_ 2 | #define WK_COMPILER_COMMON_H_ 3 | 4 | #include "common/menu.h" 5 | 6 | MenuStatus pressKeys(Menu* menu, const char* keys); 7 | 8 | #endif /* WK_COMPILER_COMMON_H_ */ 9 | -------------------------------------------------------------------------------- /src/compiler/compiler.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_COMPILER_H_ 2 | #define WK_COMPILER_COMPILER_H_ 3 | 4 | /* common includes */ 5 | #include "common/key_chord.h" 6 | #include "common/menu.h" 7 | 8 | /* local includes */ 9 | #include "token.h" 10 | #include "scanner.h" 11 | 12 | typedef struct PseudoChord PseudoChord; 13 | 14 | typedef struct 15 | { 16 | PseudoChord* chords; 17 | size_t count; 18 | size_t capacity; 19 | } PseudoChordArray; 20 | 21 | struct PseudoChord 22 | { 23 | KeyChordState state; 24 | Key key; 25 | Token keyToken; 26 | TokenArray description; 27 | TokenArray command; 28 | TokenArray before; 29 | TokenArray after; 30 | ChordFlags flags; 31 | PseudoChordArray chords; 32 | }; 33 | 34 | typedef struct 35 | { 36 | Scanner scanner; 37 | Token current; 38 | Token previous; 39 | bool hadError; 40 | bool panicMode; 41 | bool sort; 42 | bool debug; 43 | const char* delimiter; 44 | size_t delimiterLen; 45 | PseudoChord nullPseudoChord; 46 | PseudoChord chord; 47 | PseudoChordArray* chordsDest; 48 | PseudoChordArray chords; 49 | PseudoChordArray implicitArrayKeys; 50 | char* source; 51 | } Compiler; 52 | 53 | KeyChord* compileKeyChords(Compiler* compiler, Menu* menu); 54 | void initCompiler(const Menu* menu, Compiler* compiler, char* source, const char* filepath); 55 | 56 | 57 | #endif /* WK_COMPILER_COMPILER_H_ */ 58 | -------------------------------------------------------------------------------- /src/compiler/debug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* common includes */ 6 | #include "common/debug.h" 7 | 8 | /* local includes */ 9 | #include "debug.h" 10 | #include "piece_table.h" 11 | #include "token.h" 12 | 13 | void 14 | debugPrintScannedTokenFooter(void) 15 | { 16 | debugMsg(true, "| | | |"); 17 | debugPrintHeader(""); 18 | } 19 | 20 | void 21 | debugPrintScannedTokenHeader(void) 22 | { 23 | debugPrintHeader("-"); 24 | debugMsg(true, "| Scanned Tokens |"); 25 | debugPrintHeader("-"); 26 | debugMsg(true, "| Line:Col | TokenType | Lexeme |"); 27 | debugPrintHeader("-"); 28 | debugMsg(true, "| | | |"); 29 | } 30 | 31 | void 32 | disassemblePiece(const PieceTable* pieceTable, size_t index) 33 | { 34 | assert(pieceTable); 35 | 36 | Piece* piece = &pieceTable->pieces.pieces[index]; 37 | debugMsg(true, "| "); 38 | debugMsg( 39 | true, 40 | "| Source: %s", 41 | piece->source == PIECE_SOURCE_ORIGINAL 42 | ? "ORIGINAL" : "ADD" 43 | ); 44 | debugMsg(true, "| Index: %04zu", piece->index); 45 | debugMsg(true, "| Length: %04zu", piece->len); 46 | if (piece->len > 0) 47 | { 48 | debugMsg(true, "| Text: "); 49 | debugMsg(true, "| "); 50 | debugTextLenWithLineNumber(getTextAtPiece(pieceTable, piece), piece->len); 51 | } 52 | } 53 | 54 | static void 55 | disassemblePieceArray(const PieceTable* pieceTable) 56 | { 57 | assert(pieceTable); 58 | 59 | const PieceArray* array = &pieceTable->pieces; 60 | for (size_t i = 0; i < array->count; i++) 61 | { 62 | debugMsg(true, "|----------- Piece number: %04zu ------------", i); 63 | disassemblePiece(pieceTable, i); 64 | debugMsg(true, "| "); 65 | } 66 | } 67 | 68 | void 69 | disassemblePieceTable(const PieceTable* pieceTable) 70 | { 71 | assert(pieceTable); 72 | 73 | debugPrintHeader(" PieceTable "); 74 | debugMsg(true, "| "); 75 | debugMsg(true, "| Total pieces: %zu", pieceTable->pieces.count); 76 | debugMsg(true, "| Original Text length: %zu", pieceTable->originalLen); 77 | debugMsg(true, "| Add Text length: %zu", pieceTable->add.count); 78 | debugMsg(true, "| "); 79 | debugMsg(true, "|-------------- Original Text --------------"); 80 | debugMsg(true, "| "); 81 | debugTextWithLineNumber(pieceTable->original); 82 | debugMsg(true, "| "); 83 | if (pieceTable->add.string) 84 | { 85 | debugMsg(true, "|---------------- Add Text -----------------"); 86 | debugMsg(true, "| "); 87 | debugTextWithLineNumber(pieceTable->add.string); 88 | debugMsg(true, "| "); 89 | } 90 | disassemblePieceArray(pieceTable); 91 | debugPrintHeader(""); 92 | } 93 | 94 | void 95 | disassemblePseudoChord(const PseudoChord* chord) 96 | { 97 | assert(chord); 98 | 99 | debugPrintHeader(" PseudoChord "); 100 | debugMsg(true, "|"); 101 | debugMsg( 102 | true, "| State: %s", 103 | chord->state == KEY_CHORD_STATE_IS_NULL ? "STATE_IS_NULL" : "STATE_NOT_NULL" 104 | ); 105 | disassembleKeyWithoutHeader(&chord->key, 0); 106 | disassembleTokenArray(&chord->description); 107 | disassembleTokenArray(&chord->command); 108 | disassembleTokenArray(&chord->before); 109 | disassembleTokenArray(&chord->after); 110 | disassembleFlags(&chord->flags, 0); 111 | debugMsg(true, "|"); 112 | debugPrintHeader(""); 113 | } 114 | 115 | void 116 | disassembleScanner(const Scanner* scanner) 117 | { 118 | assert(scanner); 119 | 120 | debugPrintHeader(" Scanner "); 121 | debugMsg(true, "|"); 122 | debugMsg(true, "| Head: '%c'", *scanner->head); 123 | debugMsg(true, "| Start: '%c'", *scanner->start); 124 | debugMsg(true, "| Current: '%c'", *scanner->current); 125 | debugMsg(true, "| Lexeme: '%.*s'", scanner->current - scanner->start, scanner->start); 126 | debugMsg(true, "| Filepath: '%s'", scanner->filepath); 127 | debugMsg(true, "| Line: %zu", scanner->line); 128 | debugMsg(true, "| Column: %zu", scanner->column); 129 | debugMsg(true, "| Had Error: %s", scanner->hadError ? "true" : "false"); 130 | /* debugMsg(true, "| State: %s", *scanner->head); */ 131 | /* debugMsg(true, "| Prev State: %s", *scanner->head); */ 132 | debugMsg(true, "| Interp Type: %s", getTokenLiteral(scanner->interpType)); 133 | debugMsg(true, "|"); 134 | debugPrintHeader(""); 135 | } 136 | 137 | void 138 | disassembleSingleToken(const Token* token) 139 | { 140 | assert(token); 141 | 142 | debugPrintHeader("-"); 143 | disassembleToken(token); 144 | debugPrintHeader(""); 145 | } 146 | 147 | static void 148 | printErrorToken(const Token* token) 149 | { 150 | assert(token); 151 | 152 | debugMsg( 153 | true, 154 | "| %04zu:%04zu | %-27s | %-26.*s |", 155 | token->line, token->column, "TOKEN_ERROR", 156 | (int)token->length, token->start 157 | ); 158 | } 159 | 160 | static void 161 | printSimpleToken(const Token* token) 162 | { 163 | assert(token); 164 | 165 | debugMsg( 166 | true, 167 | "| %04zu:%04zu | %-27s | %-26.*s |", 168 | token->line, token->column, getTokenLiteral(token->type), 169 | (int)token->length, token->start 170 | ); 171 | } 172 | 173 | void 174 | disassembleToken(const Token* token) 175 | { 176 | assert(token); 177 | 178 | if (token->type == TOKEN_ERROR) printErrorToken(token); 179 | else printSimpleToken(token); 180 | } 181 | 182 | void 183 | disassembleTokenArray(const TokenArray* tokens) 184 | { 185 | assert(tokens); 186 | 187 | if (tokens->count == 0) return; 188 | 189 | for (size_t i = 0; i < tokens->count; i++) 190 | { 191 | disassembleToken(&tokens->tokens[i]); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/compiler/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_DEBUG_H_ 2 | #define WK_COMPILER_DEBUG_H_ 3 | 4 | #include "compiler.h" 5 | #include "piece_table.h" 6 | #include "token.h" 7 | 8 | void debugPrintScannedTokenHeader(void); 9 | void debugPrintScannedTokenFooter(void); 10 | void disassemblePiece(const PieceTable* pieceTable, size_t index); 11 | void disassemblePieceTable(const PieceTable* pieceTable); 12 | void disassemblePseudoChord(const PseudoChord* chord); 13 | void disassembleScanner(const Scanner* scanner); 14 | void disassembleSingleToken(const Token* token); 15 | void disassembleToken(const Token* token); 16 | void disassembleTokenArray(const TokenArray* tokens); 17 | 18 | #endif /* WK_COMPILER_DEBUG_H_ */ 19 | -------------------------------------------------------------------------------- /src/compiler/piece_table.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | /* common includes */ 6 | #include "common/string.h" 7 | #include "common/memory.h" 8 | 9 | /* local includes */ 10 | #include "piece_table.h" 11 | 12 | static bool 13 | isWithinOriginalBounds(PieceTable* pieceTable, const char* text) 14 | { 15 | assert(pieceTable); 16 | 17 | return ( 18 | !(text < pieceTable->original) && 19 | !(text > pieceTable->original + pieceTable->originalLen) 20 | ); 21 | } 22 | 23 | static void 24 | copyPiece(Piece* from, Piece* to) 25 | { 26 | assert(from), assert(to); 27 | 28 | to->source = from->source; 29 | to->index = from->index; 30 | to->len = from->len; 31 | } 32 | 33 | static void 34 | writePieceToPieceArray(PieceArray* array, PieceSource source, size_t index, size_t len) 35 | { 36 | assert(array); 37 | 38 | if (array->count == array->capacity) 39 | { 40 | size_t oldCapacity = array->capacity; 41 | array->capacity = GROW_CAPACITY(oldCapacity); 42 | array->pieces = GROW_ARRAY(Piece, array->pieces, oldCapacity, array->capacity); 43 | } 44 | 45 | Piece piece = { source, index, len }; 46 | copyPiece(&piece, &array->pieces[array->count]); 47 | array->count++; 48 | } 49 | 50 | void 51 | appendToPieceTable(PieceTable* pieceTable, PieceSource source, const char* text, size_t len) 52 | { 53 | assert(pieceTable); 54 | 55 | if (source == PIECE_SOURCE_ORIGINAL) 56 | { 57 | assert(isWithinOriginalBounds(pieceTable, text)); 58 | size_t index = text - pieceTable->original; 59 | writePieceToPieceArray(&pieceTable->pieces, source, index, len); 60 | return; 61 | } 62 | 63 | String* add = &pieceTable->add; 64 | size_t start = add->count; 65 | appendToString(add, text, len); 66 | writePieceToPieceArray(&pieceTable->pieces, source, start, len); 67 | } 68 | 69 | char* 70 | compilePieceTableToString(const PieceTable* pieceTable) 71 | { 72 | assert(pieceTable); 73 | 74 | String result; 75 | initString(&result); 76 | const PieceArray* pieces = &pieceTable->pieces; 77 | for (size_t i = 0; i < pieces->count; i++) 78 | { 79 | Piece* piece = &pieces->pieces[i]; 80 | appendToString(&result, getTextAtPiece(pieceTable, piece), piece->len); 81 | } 82 | 83 | return result.string; 84 | } 85 | 86 | static void 87 | freePieceArray(PieceArray* array) 88 | { 89 | assert(array); 90 | 91 | FREE_ARRAY(PieceArray, array->pieces, array->capacity); 92 | array->pieces = NULL; 93 | array->count = 0; 94 | array->capacity = 0; 95 | } 96 | 97 | void 98 | freePieceTable(PieceTable* pieceTable) 99 | { 100 | assert(pieceTable); 101 | 102 | freeString(&pieceTable->add); 103 | freePieceArray(&pieceTable->pieces); 104 | } 105 | 106 | const char* 107 | getTextAtPiece(const PieceTable* pieceTable, Piece* piece) 108 | { 109 | assert(pieceTable), assert(piece); 110 | 111 | return ( 112 | piece->source == PIECE_SOURCE_ADD 113 | ? pieceTable->add.string + piece->index 114 | : pieceTable->original + piece->index 115 | ); 116 | } 117 | 118 | static void 119 | initPieceArray(PieceArray* array) 120 | { 121 | assert(array); 122 | 123 | array->pieces = NULL; 124 | array->count = 0; 125 | array->capacity = 0; 126 | } 127 | 128 | void 129 | initPieceTable(PieceTable* pieceTable, const char* original) 130 | { 131 | assert(pieceTable), assert(original); 132 | 133 | pieceTable->original = original; 134 | pieceTable->originalLen = strlen(original); 135 | initString(&pieceTable->add); 136 | initPieceArray(&pieceTable->pieces); 137 | } 138 | -------------------------------------------------------------------------------- /src/compiler/piece_table.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_PIECE_TABLE_H_ 2 | #define WK_COMPILER_PIECE_TABLE_H_ 3 | 4 | #include 5 | 6 | /* common includes */ 7 | #include "common/string.h" 8 | 9 | typedef enum 10 | { 11 | PIECE_SOURCE_ORIGINAL, 12 | PIECE_SOURCE_ADD, 13 | } PieceSource; 14 | 15 | typedef struct 16 | { 17 | PieceSource source; 18 | size_t index; 19 | size_t len; 20 | } Piece; 21 | 22 | typedef struct 23 | { 24 | Piece* pieces; 25 | size_t capacity; 26 | size_t count; 27 | } PieceArray; 28 | 29 | typedef struct 30 | { 31 | const char* original; 32 | size_t originalLen; 33 | String add; 34 | PieceArray pieces; 35 | } PieceTable; 36 | 37 | void appendToPieceTable(PieceTable* pieceTable, PieceSource source, const char* text, size_t len); 38 | char* compilePieceTableToString(const PieceTable* pieceTable); 39 | void freePieceTable(PieceTable* pieceTable); 40 | const char* getTextAtPiece(const PieceTable* pieceTable, Piece* piece); 41 | void initPieceTable(PieceTable* pieceTable, const char* original); 42 | 43 | #endif /* WK_COMPILER_PIECE_TABLE_H_ */ 44 | -------------------------------------------------------------------------------- /src/compiler/preprocessor.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* common includes */ 10 | #include "common/common.h" 11 | #include "common/debug.h" 12 | #include "common/memory.h" 13 | #include "common/menu.h" 14 | #include "common/string.h" 15 | 16 | /* local includes */ 17 | #include "preprocessor.h" 18 | #include "debug.h" 19 | #include "piece_table.h" 20 | #include "scanner.h" 21 | #include "token.h" 22 | 23 | typedef struct 24 | { 25 | const char** files; 26 | size_t count; 27 | size_t capacity; 28 | } IncludeStack; 29 | 30 | static IncludeStack stack = {0}; 31 | 32 | static void 33 | initIncludeStack(IncludeStack* stack) 34 | { 35 | assert(stack); 36 | 37 | stack->files = NULL; 38 | stack->count = 0; 39 | stack->capacity = 0; 40 | } 41 | 42 | static void 43 | freeIncludeStack(IncludeStack* stack) 44 | { 45 | assert(stack); 46 | 47 | FREE_ARRAY(const char*, stack->files, stack->capacity); 48 | initIncludeStack(stack); 49 | } 50 | 51 | static void 52 | pushIncludeStack(IncludeStack* stack, const char* filepath) 53 | { 54 | assert(stack), assert(filepath); 55 | 56 | if (stack->count == stack->capacity) 57 | { 58 | size_t oldCapacity = stack->capacity; 59 | stack->capacity = GROW_CAPACITY(oldCapacity); 60 | stack->files = GROW_ARRAY(const char*, stack->files, oldCapacity, stack->capacity); 61 | } 62 | 63 | stack->files[stack->count++] = filepath; 64 | } 65 | 66 | static void 67 | popIncludeStack(IncludeStack* stack) 68 | { 69 | assert(stack); 70 | if (stack->count == 0) return; 71 | 72 | stack->count--; 73 | } 74 | 75 | static bool 76 | isFileInIncludeStack(IncludeStack* stack, const char* filepath) 77 | { 78 | assert(stack), assert(filepath); 79 | 80 | for (size_t i = 0; i < stack->count; i++) 81 | { 82 | if (strcmp(stack->files[i], filepath) == 0) return true; 83 | } 84 | 85 | return false; 86 | } 87 | 88 | static void 89 | disassembleIncludeStack(IncludeStack* stack) 90 | { 91 | assert(stack); 92 | 93 | char* pwd = getenv("PWD"); 94 | if (!pwd) 95 | { 96 | warnMsg("Could not get environment variable '$PWD' for include stack debug output."); 97 | pwd = ""; 98 | } 99 | size_t pwdLen = strlen(pwd); 100 | 101 | debugPrintHeader(" IncludeStack "); 102 | debugMsg(true, "|"); 103 | for (size_t i = 0; i < stack->count; i++) 104 | { 105 | const char* file = stack->files[i]; 106 | if (strncmp(file, pwd, pwdLen) == 0 && file[pwdLen] == '/') 107 | { 108 | /* File path starts with PWD, display relative path */ 109 | debugMsg(true, "| %4zu | %s", i, file + pwdLen + 1); 110 | } 111 | else 112 | { 113 | /* File path is not relative to PWD, display absolute path */ 114 | debugMsg(true, "| %4zu | %s", i, file); 115 | } 116 | } 117 | debugMsg(true, "|"); 118 | debugPrintHeader(""); 119 | } 120 | 121 | static size_t 122 | getBaseDirLength(const char* sourcePath) 123 | { 124 | assert(sourcePath); 125 | 126 | /* current moves through the 'sourcePath' 127 | * lastDir tracks the character past the last '/' 128 | * for 'sourcePath' '/home/john/wks/main.wks' 129 | * lastDir will point at the 'm' in main. 130 | * for 'sourcePath' 'wks/main.wks' the same is true. 131 | * for 'sourcePath' 'main.wks' it will still point at 'm'. 132 | * The function returns the length of the baseDir, 133 | * relative or absolute makes no difference here. */ 134 | const char* current = sourcePath; 135 | const char* lastDir = sourcePath; 136 | 137 | while (*current != '\0') 138 | { 139 | char c = *current++; 140 | if (c == '/') lastDir = current; 141 | } 142 | 143 | return lastDir - sourcePath; 144 | } 145 | 146 | static char* 147 | getAbsolutePath(const char* filepath, size_t len, const char* sourcePath) 148 | { 149 | assert(filepath), assert(sourcePath); 150 | 151 | size_t baseLen = getBaseDirLength(sourcePath); 152 | String includeFilePath = {0}; 153 | initString(&includeFilePath); 154 | 155 | /* If filepath is not absolute and source path is not in the PWD add base path */ 156 | if (*filepath != '/' && baseLen) appendToString(&includeFilePath, sourcePath, baseLen); 157 | 158 | appendToString(&includeFilePath, filepath, len); 159 | char* result = realpath(includeFilePath.string, NULL); 160 | if (!result) 161 | { 162 | warnMsg("Could not get the realpath for file: '%.*s'.", len, filepath); 163 | disownString(&includeFilePath); 164 | return includeFilePath.string; 165 | } 166 | 167 | freeString(&includeFilePath); 168 | return result; 169 | } 170 | 171 | static void 172 | handleIncludeMacro(Menu* menu, Scanner* scanner, PieceTable* result, Token* includeFile) 173 | { 174 | assert(menu), assert(scanner), assert(result), assert(includeFile); 175 | 176 | /* currently pointing at the 'i' in ':include', so take off one. */ 177 | const char* sourcePath = scanner->filepath; 178 | 179 | if (!sourcePath) sourcePath = getenv("PWD"); 180 | if (!sourcePath) 181 | { 182 | warnMsg("Could not get environment variable '$PWD' for script."); 183 | } 184 | 185 | char* includeFilePath = NULL; 186 | char* includeSource = NULL; 187 | char* includeResult = NULL; 188 | 189 | /* Get the path to the included file */ 190 | includeFilePath = getAbsolutePath( 191 | includeFile->start, includeFile->length, sourcePath 192 | ); 193 | if (!includeFilePath) 194 | { 195 | errorMsg("Failed to get the included file path."); 196 | scanner->hadError = true; 197 | goto fail; 198 | } 199 | 200 | if (isFileInIncludeStack(&stack, includeFilePath)) 201 | { 202 | printf( 203 | "%s:%u:%u: wk does not support circular includes: ':include \"%.*s\"'.\n", 204 | sourcePath, includeFile->line, includeFile->column, 205 | (int)includeFile->length, includeFile->start 206 | ); 207 | if (menu->debug) disassembleIncludeStack(&stack); 208 | scanner->hadError = true; 209 | goto fail; 210 | } 211 | 212 | /* Try to read the included file */ 213 | includeSource = readFile(includeFilePath); 214 | if (!includeSource) 215 | { 216 | /* readFile prints an error for us. */ 217 | scanner->hadError = true; 218 | goto fail; 219 | } 220 | 221 | /* check that the file and the source are not one and the same */ 222 | if (strcmp(includeSource, scanner->head) == 0) 223 | { 224 | errorMsg( 225 | "Included file appears to be the same as the source file. Cannot `:include` self." 226 | ); 227 | scanner->hadError = true; 228 | goto fail; 229 | } 230 | 231 | /* Run preprocessor on the included file */ 232 | includeResult = runPreprocessor(menu, includeSource, includeFilePath); 233 | if (!includeResult) 234 | { 235 | errorMsg("Failed to get preprocessor result."); 236 | scanner->hadError = true; 237 | goto fail; 238 | } 239 | 240 | /* Append the result. */ 241 | appendToPieceTable(result, PIECE_SOURCE_ADD, includeResult, strlen(includeResult)); 242 | 243 | fail: 244 | free(includeFilePath); 245 | free(includeSource); 246 | free(includeResult); 247 | return; 248 | } 249 | 250 | static void 251 | handleMacroWithStringArg(Menu* menu, Scanner* scanner, Token* token, PieceTable* pieceTable) 252 | { 253 | assert(scanner), assert(menu), assert(token), assert(pieceTable); 254 | 255 | makeScannerCurrent(scanner); 256 | 257 | Token result = {0}; 258 | scanTokenForPreprocessor(scanner, &result, SCANNER_WANTS_DESCRIPTION); 259 | if (result.type != TOKEN_DESCRIPTION) 260 | { 261 | errorAtToken( 262 | token, scanner->filepath, 263 | "Expect string argument to macro. Got '%.*s'.", 264 | token->length, token->start, 265 | result.length, result.start 266 | ); 267 | scanner->hadError = true; 268 | return; 269 | } 270 | 271 | String arg = {0}; 272 | initString(&arg); 273 | appendToString(&arg, result.start, result.length); 274 | 275 | switch (token->type) 276 | { 277 | case TOKEN_FOREGROUND_COLOR: 278 | { 279 | setMenuColor(menu, arg.string, MENU_COLOR_KEY); 280 | setMenuColor(menu, arg.string, MENU_COLOR_DELIMITER); 281 | setMenuColor(menu, arg.string, MENU_COLOR_PREFIX); 282 | menu->garbage.foregroundKeyColor = arg.string; 283 | break; 284 | } 285 | case TOKEN_FOREGROUND_KEY_COLOR: 286 | { 287 | setMenuColor(menu, arg.string, MENU_COLOR_KEY); 288 | menu->garbage.foregroundKeyColor = arg.string; 289 | break; 290 | } 291 | case TOKEN_FOREGROUND_DELIMITER_COLOR: 292 | { 293 | setMenuColor(menu, arg.string, MENU_COLOR_DELIMITER); 294 | menu->garbage.foregroundDelimiterColor= arg.string; 295 | break; 296 | } 297 | case TOKEN_FOREGROUND_PREFIX_COLOR: 298 | { 299 | setMenuColor(menu, arg.string, MENU_COLOR_PREFIX); 300 | menu->garbage.foregroundPrefixColor = arg.string; 301 | break; 302 | } 303 | case TOKEN_FOREGROUND_CHORD_COLOR: 304 | { 305 | setMenuColor(menu, arg.string, MENU_COLOR_CHORD); 306 | menu->garbage.foregroundChordColor = arg.string; 307 | break; 308 | } 309 | case TOKEN_BACKGROUND_COLOR: 310 | { 311 | setMenuColor(menu, arg.string, MENU_COLOR_BACKGROUND); 312 | menu->garbage.backgroundColor = arg.string; 313 | break; 314 | } 315 | case TOKEN_BORDER_COLOR: 316 | { 317 | setMenuColor(menu, arg.string, MENU_COLOR_BORDER); 318 | menu->garbage.borderColor = arg.string; 319 | break; 320 | } 321 | case TOKEN_SHELL: menu->shell = menu->garbage.shell = arg.string; break; 322 | case TOKEN_FONT: menu->font = menu->garbage.font = arg.string; break; 323 | case TOKEN_IMPLICIT_ARRAY_KEYS: 324 | { 325 | menu->implicitArrayKeys = menu->garbage.implicitArrayKeys = arg.string; 326 | break; 327 | } 328 | case TOKEN_INCLUDE: 329 | { 330 | freeString(&arg); 331 | handleIncludeMacro(menu, scanner, pieceTable, &result); 332 | break; 333 | } 334 | default: 335 | { 336 | errorMsg( 337 | "Got an unexpected token to function `handleMacroWithStringArg`." 338 | ); 339 | scanner->hadError = true; 340 | break; 341 | } 342 | } 343 | 344 | disownString(&arg); 345 | } 346 | 347 | static void 348 | handleMacroWithDoubleArg(Scanner* scanner, Menu* menu, Token* token) 349 | { 350 | assert(scanner), assert(menu), assert(token); 351 | 352 | makeScannerCurrent(scanner); 353 | 354 | Token result = {0}; 355 | scanTokenForPreprocessor(scanner, &result, SCANNER_WANTS_DOUBLE); 356 | if (result.type != TOKEN_DOUBLE) goto fail; 357 | 358 | double value = 0; 359 | if (!getDoubleFromToken(&result, &value, menu->debug)) goto fail; 360 | 361 | switch (token->type) 362 | { 363 | case TOKEN_BORDER_RADIUS: menu->borderRadius = value; return; 364 | default: 365 | { 366 | errorMsg( 367 | "Got an unexpected token to function `handleSwitchWithNumberArg`." 368 | ); 369 | scanner->hadError = true; 370 | return; 371 | } 372 | } 373 | 374 | fail: 375 | errorAtToken( 376 | token, scanner->filepath, 377 | "Expect double argument to macro. Got '%.*s'.", 378 | token->length, token->start, 379 | result.length, result.start 380 | ); 381 | scanner->hadError = true; 382 | return; 383 | } 384 | 385 | static void 386 | handleMacroWithInt32Arg(Scanner* scanner, Menu* menu, Token* token) 387 | { 388 | assert(scanner), assert(menu), assert(token); 389 | 390 | makeScannerCurrent(scanner); 391 | 392 | Token result = {0}; 393 | scanTokenForPreprocessor(scanner, &result, SCANNER_WANTS_INTEGER); 394 | if (result.type != TOKEN_INTEGER) goto fail; 395 | 396 | int32_t value = 0; 397 | if (!getInt32FromToken(&result, &value, menu->debug)) goto fail; 398 | 399 | switch (token->type) 400 | { 401 | case TOKEN_MENU_WIDTH: menu->menuWidth = value; return; 402 | case TOKEN_MENU_GAP: menu->menuGap = value; return; 403 | default: 404 | { 405 | errorMsg( 406 | "Got an unexpected token to function `handleSwitchWithInt32Arg`." 407 | ); 408 | scanner->hadError = true; 409 | return; 410 | } 411 | } 412 | 413 | fail: 414 | errorAtToken( 415 | token, scanner->filepath, 416 | "Expect integer argument to macro. Got '%.*s'.", 417 | token->length, token->start, 418 | result.length, result.start 419 | ); 420 | scanner->hadError = true; 421 | return; 422 | } 423 | 424 | static void 425 | handleMacroWithUint32Arg(Scanner* scanner, Menu* menu, Token* token) 426 | { 427 | assert(scanner), assert(menu), assert(token); 428 | 429 | makeScannerCurrent(scanner); 430 | 431 | Token result = {0}; 432 | scanTokenForPreprocessor(scanner, &result, SCANNER_WANTS_UNSIGNED_INTEGER); 433 | if (result.type != TOKEN_UNSIGNED_INTEGER) goto fail; 434 | 435 | uint32_t value = 0; 436 | if (!getUint32FromToken(&result, &value, menu->debug)) goto fail; 437 | 438 | switch (token->type) 439 | { 440 | case TOKEN_MAX_COLUMNS: menu->maxCols = value; return; 441 | case TOKEN_BORDER_WIDTH: menu->borderWidth = value; return; 442 | case TOKEN_WIDTH_PADDING: menu->wpadding = value; return; 443 | case TOKEN_HEIGHT_PADDING: menu->hpadding = value; return; 444 | case TOKEN_MENU_DELAY: menu->delay = value; return; 445 | default: 446 | { 447 | errorMsg( 448 | "Got an unexpected token to function `handleSwitchWithUint32Arg`." 449 | ); 450 | scanner->hadError = true; 451 | return; 452 | } 453 | } 454 | 455 | fail: 456 | errorAtToken( 457 | token, scanner->filepath, 458 | "Expect unsigned integer argument to macro. Got '%.*s'.", 459 | token->length, token->start, 460 | result.length, result.start 461 | ); 462 | scanner->hadError = true; 463 | return; 464 | } 465 | 466 | char* 467 | runPreprocessor(Menu* menu, const char* source, const char* filepath) 468 | { 469 | assert(menu), assert(source); 470 | 471 | Scanner scanner = {0}; 472 | initScanner(&scanner, source, filepath); 473 | const char* scannerStart = scanner.head; 474 | 475 | PieceTable pieceTable; 476 | initPieceTable(&pieceTable, source); 477 | 478 | /* Only init once. */ 479 | if (stack.count == 0) initIncludeStack(&stack); 480 | char* absoluteFilePath = getAbsolutePath(filepath, strlen(filepath), "."); 481 | pushIncludeStack(&stack, absoluteFilePath); 482 | if (menu->debug) disassembleIncludeStack(&stack); 483 | 484 | while (!isAtEnd(&scanner)) 485 | { 486 | if (scanner.hadError) goto fail; 487 | 488 | Token token = {0}; 489 | scanTokenForPreprocessor(&scanner, &token, SCANNER_WANTS_MACRO); 490 | if (token.type == TOKEN_EOF) break; 491 | if (menu->debug) disassembleSingleToken(&token); 492 | 493 | /* Found either valid preprocessor token, or error. Either way it is safe to append. */ 494 | appendToPieceTable( 495 | &pieceTable, PIECE_SOURCE_ORIGINAL, scannerStart, scanner.start - 1 - scannerStart 496 | ); 497 | 498 | /* Handle macros */ 499 | switch (token.type) 500 | { 501 | /* Switches with no args. */ 502 | case TOKEN_DEBUG: menu->debug = true; break; 503 | case TOKEN_SORT: menu->sort = true; break; 504 | case TOKEN_TOP: menu->position = MENU_POS_TOP; break; 505 | case TOKEN_BOTTOM: menu->position = MENU_POS_BOTTOM; break; 506 | 507 | /* Switches with signed integer args. */ 508 | case TOKEN_MENU_WIDTH: /* FALLTHROUGH */ 509 | case TOKEN_MENU_GAP: handleMacroWithInt32Arg(&scanner, menu, &token); break; 510 | 511 | /* Switches with unsigned integer args. */ 512 | case TOKEN_MAX_COLUMNS: /* FALLTHROUGH */ 513 | case TOKEN_BORDER_WIDTH: 514 | case TOKEN_WIDTH_PADDING: 515 | case TOKEN_HEIGHT_PADDING: 516 | case TOKEN_MENU_DELAY: handleMacroWithUint32Arg(&scanner, menu, &token); break; 517 | 518 | /* Switches with double args. */ 519 | case TOKEN_BORDER_RADIUS: handleMacroWithDoubleArg(&scanner, menu, &token); break; 520 | 521 | /* Switches with string args. */ 522 | case TOKEN_FOREGROUND_COLOR: /* FALLTHROUGH */ 523 | case TOKEN_FOREGROUND_KEY_COLOR: 524 | case TOKEN_FOREGROUND_DELIMITER_COLOR: 525 | case TOKEN_FOREGROUND_PREFIX_COLOR: 526 | case TOKEN_FOREGROUND_CHORD_COLOR: 527 | case TOKEN_BACKGROUND_COLOR: 528 | case TOKEN_BORDER_COLOR: 529 | case TOKEN_SHELL: 530 | case TOKEN_FONT: 531 | case TOKEN_IMPLICIT_ARRAY_KEYS: 532 | case TOKEN_INCLUDE: handleMacroWithStringArg(menu, &scanner, &token, &pieceTable); break; 533 | 534 | /* Handle error */ 535 | case TOKEN_ERROR: 536 | { 537 | errorAtToken( 538 | &token, scanner.filepath, 539 | "%s", token.message 540 | ); 541 | scanner.hadError = true; 542 | break; 543 | } 544 | default: 545 | { 546 | if (menu->debug) 547 | { 548 | /* DEBUG HERE */ 549 | } 550 | errorAtToken( 551 | &token, scanner.filepath, 552 | "Got unexpected token during preprocessor parsing." 553 | ); 554 | scanner.hadError = true; 555 | break; 556 | } 557 | } 558 | 559 | /* Update scanner for the next go around. */ 560 | scannerStart = scanner.current; 561 | } 562 | 563 | /* Append the last bit of the source to result. */ 564 | appendToPieceTable( 565 | &pieceTable, PIECE_SOURCE_ORIGINAL, scannerStart, scanner.current - scannerStart 566 | ); 567 | 568 | if (menu->debug) 569 | { 570 | disassemblePieceTable(&pieceTable); 571 | disassembleMenu(menu); 572 | } 573 | char* result = compilePieceTableToString(&pieceTable); 574 | 575 | fail: 576 | popIncludeStack(&stack); 577 | if (stack.count == 0) freeIncludeStack(&stack); 578 | free(absoluteFilePath); 579 | freePieceTable(&pieceTable); 580 | return scanner.hadError ? NULL : result; 581 | } 582 | -------------------------------------------------------------------------------- /src/compiler/preprocessor.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_PREPROCESSOR_H_ 2 | #define WK_COMPILER_PREPROCESSOR_H_ 3 | 4 | #include 5 | 6 | /* common includes */ 7 | #include "common/menu.h" 8 | 9 | char* runPreprocessor(Menu* menu, const char* source, const char* sourcePath); 10 | 11 | #endif /* WK_COMPILER_PREPROCESSOR_H_ */ 12 | -------------------------------------------------------------------------------- /src/compiler/scanner.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_SCANNER_H_ 2 | #define WK_COMPILER_SCANNER_H_ 3 | 4 | #include 5 | #include 6 | 7 | /* local includes */ 8 | #include "common/key_chord.h" 9 | #include "token.h" 10 | 11 | typedef enum 12 | { 13 | SCANNER_WANTS_MACRO, 14 | SCANNER_WANTS_DESCRIPTION, 15 | SCANNER_WANTS_DOUBLE, 16 | SCANNER_WANTS_INTEGER, 17 | SCANNER_WANTS_UNSIGNED_INTEGER, 18 | } ScannerFlag; 19 | 20 | typedef enum 21 | { 22 | SCANNER_STATE_NORMAL, 23 | SCANNER_STATE_DESCRIPTION, 24 | SCANNER_STATE_COMMAND, 25 | SCANNER_STATE_INTERPOLATION, 26 | } ScannerState; 27 | 28 | typedef struct 29 | { 30 | const char* head; 31 | const char* start; 32 | const char* current; 33 | const char* filepath; 34 | size_t line; 35 | size_t column; 36 | bool hadError; 37 | SpecialKey special; 38 | ScannerState state; 39 | ScannerState previousState; 40 | TokenType interpType; 41 | } Scanner; 42 | 43 | void initScanner(Scanner* scanner, const char* source, const char* filepath); 44 | bool isAtEnd(const Scanner* scanner); 45 | void makeScannerCurrent(Scanner* scanner); 46 | void scanTokenForCompiler(Scanner* scanner, Token* result); 47 | void scanTokenForPreprocessor(Scanner* scanner, Token* result, ScannerFlag flag); 48 | 49 | #endif /* WK_COMPILER_SCANNER_H_ */ 50 | -------------------------------------------------------------------------------- /src/compiler/token.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /* common includes */ 12 | #include "common/debug.h" 13 | #include "common/memory.h" 14 | 15 | /* local includes */ 16 | #include "token.h" 17 | 18 | void 19 | copyToken(const Token* from, Token* to) 20 | { 21 | assert(from), assert(to); 22 | 23 | to->type = from->type; 24 | to->start = from->start; 25 | to->length = from->length; 26 | to->line = from->line; 27 | to->column = from->column; 28 | to->message = from->message; 29 | to->messageLength = from->messageLength; 30 | } 31 | 32 | void 33 | copyTokenArray(const TokenArray* from, TokenArray* to) 34 | { 35 | assert(from), assert(to); 36 | 37 | if (from->count == 0) 38 | { 39 | initTokenArray(to); 40 | return; 41 | } 42 | to->tokens = ALLOCATE(Token, from->capacity); 43 | to->count = from->count; 44 | to->capacity = from->capacity; 45 | for (size_t i = 0; i < from->count; i++) 46 | { 47 | copyToken(&from->tokens[i], &to->tokens[i]); 48 | } 49 | } 50 | 51 | void 52 | errorAtToken(const Token* token, const char* filepath, const char* fmt, ...) 53 | { 54 | assert(token), assert(filepath), assert(fmt); 55 | 56 | fprintf(stderr, "%s:%u:%u: error", filepath, token->line, token->column); 57 | 58 | if (token->type == TOKEN_EOF) 59 | { 60 | fprintf(stderr, " at end: "); 61 | } 62 | else if (token->type == TOKEN_ERROR) 63 | { 64 | fprintf( 65 | stderr, 66 | " at line %u: '%.*s': ", 67 | token->line, (int)token->length, token->start 68 | ); 69 | } 70 | else 71 | { 72 | fprintf(stderr, " at '%.*s': ", (int)token->length, token->start); 73 | } 74 | 75 | va_list ap; 76 | va_start(ap, fmt); 77 | vfprintf(stderr, fmt, ap); 78 | va_end(ap); 79 | 80 | fputc('\n', stderr); 81 | } 82 | 83 | bool 84 | getDoubleFromToken(const Token* token, double* dest, bool debug) 85 | { 86 | assert(token), assert(dest); 87 | 88 | char* endptr; 89 | int oldErrno = errno; 90 | errno = 0; 91 | double value = strtod(token->start, &endptr); 92 | *dest = 0; 93 | 94 | if (errno == ERANGE) 95 | { 96 | debugMsg( 97 | debug, 98 | "Tried to parse string as double, but it was out of range: '%.*s'", 99 | token->length, token->start 100 | ); 101 | errno = oldErrno; 102 | return false; 103 | } 104 | else if ((size_t)(endptr - token->start) != token->length) 105 | { 106 | debugMsg( 107 | debug, 108 | "Tried to parse string as double, but it was parsed differently than expected.\n\t" 109 | "String to be parsed: '%.*s'\n\t" 110 | "String actually parsed: '%.*s'", 111 | token->length, token->start, 112 | (int)(endptr - token->start), token->start 113 | ); 114 | return false; 115 | } 116 | 117 | *dest = (int32_t)value; 118 | return true; 119 | } 120 | 121 | bool 122 | getInt32FromToken(const Token* token, int32_t* dest, bool debug) 123 | { 124 | assert(token), assert(dest); 125 | 126 | char* endptr; 127 | int oldErrno = errno; 128 | errno = 0; 129 | intmax_t value = strtoimax(token->start, &endptr, 10); 130 | *dest = 0; 131 | 132 | if (errno == ERANGE) 133 | { 134 | debugMsg( 135 | debug, 136 | "Tried to parse string as uintmax_t, but it was out of range: '%.*s'", 137 | token->length, token->start 138 | ); 139 | errno = oldErrno; 140 | return false; 141 | } 142 | else if (value > INT32_MAX) 143 | { 144 | debugMsg( 145 | debug, 146 | "Tried to parse string as int32_t, but it was too large: '%.*s'", 147 | token->length, token->start 148 | ); 149 | return false; 150 | } 151 | else if ((size_t)(endptr - token->start) != token->length) 152 | { 153 | debugMsg( 154 | debug, 155 | "Tried to parse string as int32_t, but it was parsed differently than expected.\n\t" 156 | "String to be parsed: '%.*s'\n\t" 157 | "String actually parsed: '%.*s'", 158 | token->length, token->start, 159 | (int)(endptr - token->start), token->start 160 | ); 161 | return false; 162 | } 163 | 164 | *dest = (int32_t)value; 165 | return true; 166 | } 167 | 168 | bool 169 | getUint32FromToken(const Token* token, uint32_t* dest, bool debug) 170 | { 171 | assert(token), assert(dest); 172 | 173 | char* endptr; 174 | int oldErrno = errno; 175 | errno = 0; 176 | uintmax_t value = strtoumax(token->start, &endptr, 10); 177 | *dest = 0; 178 | 179 | if (errno == ERANGE) 180 | { 181 | debugMsg( 182 | debug, 183 | "Tried to parse string as uintmax_t, but it was out of range: '%.*s'", 184 | token->length, token->start 185 | ); 186 | errno = oldErrno; 187 | return false; 188 | } 189 | else if (value > UINT32_MAX) 190 | { 191 | debugMsg( 192 | debug, 193 | "Tried to parse string as uint32_t, but it was too large: '%.*s'", 194 | token->length, token->start 195 | ); 196 | return false; 197 | } 198 | else if ((size_t)(endptr - token->start) != token->length) 199 | { 200 | debugMsg( 201 | debug, 202 | "Tried to parse string as uint32_t, but it was parsed differently than expected.\n\t" 203 | "String to be parsed: '%.*s'\n\t" 204 | "String actually parsed: '%.*s'", 205 | token->length, token->start, 206 | (int)(endptr - token->start), token->start 207 | ); 208 | return false; 209 | } 210 | 211 | *dest = (uint32_t)value; 212 | return true; 213 | } 214 | 215 | const char* 216 | getTokenLiteral(const TokenType type) 217 | { 218 | switch (type) 219 | { 220 | /* single characters */ 221 | case TOKEN_LEFT_BRACKET: return "TOKEN_LEFT_BRACKET"; 222 | case TOKEN_RIGHT_BRACKET: return "TOKEN_RIGHT_BRACKET"; 223 | case TOKEN_LEFT_BRACE: return "TOKEN_LEFT_BRACE"; 224 | case TOKEN_RIGHT_BRACE: return "TOKEN_RIGHT_BRACE"; 225 | case TOKEN_LEFT_PAREN: return "TOKEN_LEFT_PAREN"; 226 | case TOKEN_RIGHT_PAREN: return "TOKEN_RIGHT_PAREN"; 227 | case TOKEN_ELLIPSIS: return "TOKEN_ELLIPSIS"; 228 | 229 | /* preprocessor macros */ 230 | 231 | /* string macros */ 232 | case TOKEN_INCLUDE: return "TOKEN_INCLUDE"; 233 | case TOKEN_FOREGROUND_COLOR: return "TOKEN_FOREGROUND_COLOR"; 234 | case TOKEN_FOREGROUND_KEY_COLOR: return "TOKEN_FOREGROUND_KEY_COLOR"; 235 | case TOKEN_FOREGROUND_DELIMITER_COLOR: return "TOKEN_FOREGROUND_DELIMITER_COLOR"; 236 | case TOKEN_FOREGROUND_PREFIX_COLOR: return "TOKEN_FOREGROUND_PREFIX_COLOR"; 237 | case TOKEN_FOREGROUND_CHORD_COLOR: return "TOKEN_FOREGROUND_CHORD_COLOR"; 238 | case TOKEN_BACKGROUND_COLOR: return "TOKEN_BACKGROUND_COLOR"; 239 | case TOKEN_BORDER_COLOR: return "TOKEN_BORDER_COLOR"; 240 | case TOKEN_SHELL: return "TOKEN_SHELL"; 241 | case TOKEN_FONT: return "TOKEN_FONT"; 242 | case TOKEN_IMPLICIT_ARRAY_KEYS: return "TOKEN_IMPLICIT_ARRAY_KEYS"; 243 | 244 | /* switch macros */ 245 | case TOKEN_DEBUG: return "TOKEN_DEBUG"; 246 | case TOKEN_SORT: return "TOKEN_SORT"; 247 | case TOKEN_TOP: return "TOKEN_TOP"; 248 | case TOKEN_BOTTOM: return "TOKEN_BOTTOM"; 249 | 250 | /* [-]integer macros */ 251 | case TOKEN_MENU_WIDTH: return "TOKEN_MENU_WIDTH"; 252 | case TOKEN_MENU_GAP: return "TOKEN_MENU_GAP"; 253 | 254 | /* integer macros */ 255 | case TOKEN_MAX_COLUMNS: return "TOKEN_MAX_COLUMNS"; 256 | case TOKEN_BORDER_WIDTH: return "TOKEN_BORDER_WIDTH"; 257 | case TOKEN_WIDTH_PADDING: return "TOKEN_WIDTH_PADDING"; 258 | case TOKEN_HEIGHT_PADDING: return "TOKEN_HEIGHT_PADDING"; 259 | case TOKEN_MENU_DELAY: return "TOKEN_MENU_DELAY"; 260 | 261 | /* number macros */ 262 | case TOKEN_BORDER_RADIUS: return "TOKEN_BORDER_RADIUS"; 263 | 264 | /* identifiers */ 265 | case TOKEN_THIS_KEY: return "TOKEN_THIS_KEY"; 266 | case TOKEN_INDEX: return "TOKEN_INDEX"; 267 | case TOKEN_INDEX_ONE: return "TOKEN_INDEX_ONE"; 268 | case TOKEN_THIS_DESC: return "TOKEN_THIS_DESC"; 269 | case TOKEN_THIS_DESC_UPPER_FIRST: return "TOKEN_THIS_DESC_UPPER_FIRST"; 270 | case TOKEN_THIS_DESC_LOWER_FIRST: return "TOKEN_THIS_DESC_LOWER_FIRST"; 271 | case TOKEN_THIS_DESC_UPPER_ALL: return "TOKEN_THIS_DESC_UPPER_ALL"; 272 | case TOKEN_THIS_DESC_LOWER_ALL: return "TOKEN_THIS_DESC_LOWER_ALL"; 273 | 274 | /* hooks */ 275 | case TOKEN_BEFORE: return "TOKEN_BEFORE"; 276 | case TOKEN_AFTER: return "TOKEN_AFTER"; 277 | case TOKEN_SYNC_BEFORE: return "TOKEN_SYNC_BEFORE"; 278 | case TOKEN_SYNC_AFTER: return "TOKEN_SYNC_AFTER"; 279 | 280 | /* flags */ 281 | case TOKEN_KEEP: return "TOKEN_KEEP"; 282 | case TOKEN_CLOSE: return "TOKEN_CLOSE"; 283 | case TOKEN_INHERIT: return "TOKEN_INHERIT"; 284 | case TOKEN_IGNORE: return "TOKEN_IGNORE"; 285 | case TOKEN_IGNORE_SORT: return "TOKEN_IGNORE_SORT"; 286 | case TOKEN_UNHOOK: return "TOKEN_UNHOOK"; 287 | case TOKEN_DEFLAG: return "TOKEN_DEFLAG"; 288 | case TOKEN_NO_BEFORE: return "TOKEN_NO_BEFORE"; 289 | case TOKEN_NO_AFTER: return "TOKEN_NO_AFTER"; 290 | case TOKEN_WRITE: return "TOKEN_WRITE"; 291 | case TOKEN_SYNC_CMD: return "TOKEN_SYNC_CMD"; 292 | 293 | /* literals */ 294 | case TOKEN_COMMAND: return "TOKEN_COMMAND"; 295 | case TOKEN_DESCRIPTION: return "TOKEN_DESCRIPTION"; 296 | case TOKEN_DOUBLE: return "TOKEN_DOUBLE"; 297 | case TOKEN_INTEGER: return "TOKEN_INTEGER"; 298 | case TOKEN_UNSIGNED_INTEGER: return "TOKEN_UNSIGNED_INTEGER"; 299 | 300 | /* interpolations */ 301 | case TOKEN_COMM_INTERP: return "TOKEN_COMM_INTERP"; 302 | case TOKEN_DESC_INTERP: return "TOKEN_DESC_INTERP"; 303 | 304 | /* keys */ 305 | case TOKEN_KEY: return "TOKEN_KEY"; 306 | 307 | /* mods */ 308 | case TOKEN_MOD_CTRL: return "TOKEN_MOD_CTRL"; 309 | case TOKEN_MOD_ALT: return "TOKEN_MOD_ALT"; 310 | case TOKEN_MOD_HYPER: return "TOKEN_MOD_HYPER"; 311 | case TOKEN_MOD_SHIFT: return "TOKEN_MOD_SHIFT"; 312 | 313 | /* special keys */ 314 | case TOKEN_SPECIAL_KEY: return "TOKEN_SPECIAL_KEY"; 315 | 316 | /* control */ 317 | case TOKEN_NO_INTERP: return "TOKEN_NO_INTERP"; 318 | case TOKEN_ERROR: return "TOKEN_ERROR"; 319 | case TOKEN_EOF: return "TOKEN_EOF"; 320 | case TOKEN_EMPTY: return "TOKEN_EMPTY"; 321 | } 322 | 323 | return "TOKEN_UNKNOWN"; 324 | } 325 | 326 | void 327 | initToken(Token* token) 328 | { 329 | assert(token); 330 | 331 | token->type = TOKEN_EMPTY; 332 | token->start = NULL; 333 | token->length = 0; 334 | token->line = 0; 335 | token->column = 0; 336 | token->message = NULL; 337 | token->messageLength = 0; 338 | } 339 | 340 | void 341 | initTokenArray(TokenArray* tokens) 342 | { 343 | assert(tokens); 344 | 345 | tokens->tokens = NULL; 346 | tokens->count = 0; 347 | tokens->capacity = 0; 348 | } 349 | 350 | void 351 | freeTokenArray(TokenArray* tokens) 352 | { 353 | assert(tokens); 354 | 355 | FREE_ARRAY(Token, tokens->tokens, tokens->capacity); 356 | tokens->tokens = NULL; 357 | tokens->count = 0; 358 | tokens->capacity = 0; 359 | } 360 | 361 | bool 362 | isTokenHookType(const TokenType type) 363 | { 364 | return ( 365 | type == TOKEN_BEFORE || type == TOKEN_AFTER || 366 | type == TOKEN_SYNC_BEFORE || type == TOKEN_SYNC_AFTER 367 | ); 368 | } 369 | 370 | bool 371 | isTokenModType(const TokenType type) 372 | { 373 | return ( 374 | type == TOKEN_MOD_CTRL || type == TOKEN_MOD_ALT || 375 | type == TOKEN_MOD_HYPER || type == TOKEN_MOD_SHIFT 376 | ); 377 | } 378 | 379 | void 380 | writeTokenArray(TokenArray* tokens, Token* token) 381 | { 382 | assert(tokens), assert(token); 383 | 384 | if (tokens->count == tokens->capacity) 385 | { 386 | size_t oldCapacity = tokens->capacity; 387 | tokens->capacity = GROW_CAPACITY(oldCapacity); 388 | tokens->tokens = GROW_ARRAY( 389 | Token, tokens->tokens, oldCapacity, tokens->capacity 390 | ); 391 | } 392 | 393 | copyToken(token, &tokens->tokens[tokens->count]); 394 | tokens->count++; 395 | } 396 | -------------------------------------------------------------------------------- /src/compiler/token.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_TOKEN_H_ 2 | #define WK_COMPILER_TOKEN_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef enum 9 | { 10 | /* single characters */ 11 | TOKEN_LEFT_BRACKET, 12 | TOKEN_RIGHT_BRACKET, 13 | TOKEN_LEFT_BRACE, 14 | TOKEN_RIGHT_BRACE, 15 | TOKEN_LEFT_PAREN, 16 | TOKEN_RIGHT_PAREN, 17 | TOKEN_ELLIPSIS, 18 | 19 | /* preprocessor macros */ 20 | 21 | /* string macros */ 22 | TOKEN_INCLUDE, 23 | TOKEN_FOREGROUND_COLOR, 24 | TOKEN_FOREGROUND_KEY_COLOR, 25 | TOKEN_FOREGROUND_DELIMITER_COLOR, 26 | TOKEN_FOREGROUND_PREFIX_COLOR, 27 | TOKEN_FOREGROUND_CHORD_COLOR, 28 | TOKEN_BACKGROUND_COLOR, 29 | TOKEN_BORDER_COLOR, 30 | TOKEN_SHELL, 31 | TOKEN_FONT, 32 | TOKEN_IMPLICIT_ARRAY_KEYS, 33 | 34 | /* switch macros */ 35 | TOKEN_DEBUG, 36 | TOKEN_SORT, 37 | TOKEN_TOP, 38 | TOKEN_BOTTOM, 39 | 40 | /* [-]integer macros */ 41 | TOKEN_MENU_WIDTH, 42 | TOKEN_MENU_GAP, 43 | 44 | /* integer macros */ 45 | TOKEN_MAX_COLUMNS, 46 | TOKEN_BORDER_WIDTH, 47 | TOKEN_WIDTH_PADDING, 48 | TOKEN_HEIGHT_PADDING, 49 | TOKEN_MENU_DELAY, 50 | 51 | /* number macros */ 52 | TOKEN_BORDER_RADIUS, 53 | 54 | /* identifiers */ 55 | TOKEN_THIS_KEY, 56 | TOKEN_INDEX, 57 | TOKEN_INDEX_ONE, 58 | TOKEN_THIS_DESC, 59 | TOKEN_THIS_DESC_UPPER_FIRST, 60 | TOKEN_THIS_DESC_LOWER_FIRST, 61 | TOKEN_THIS_DESC_UPPER_ALL, 62 | TOKEN_THIS_DESC_LOWER_ALL, 63 | 64 | /* hooks */ 65 | TOKEN_BEFORE, 66 | TOKEN_AFTER, 67 | TOKEN_SYNC_BEFORE, 68 | TOKEN_SYNC_AFTER, 69 | 70 | /* flags */ 71 | TOKEN_KEEP, 72 | TOKEN_CLOSE, 73 | TOKEN_INHERIT, 74 | TOKEN_IGNORE, 75 | TOKEN_IGNORE_SORT, 76 | TOKEN_UNHOOK, 77 | TOKEN_DEFLAG, 78 | TOKEN_NO_BEFORE, 79 | TOKEN_NO_AFTER, 80 | TOKEN_WRITE, 81 | TOKEN_SYNC_CMD, 82 | 83 | /* literals */ 84 | TOKEN_COMMAND, 85 | TOKEN_DESCRIPTION, 86 | TOKEN_DOUBLE, 87 | TOKEN_INTEGER, 88 | TOKEN_UNSIGNED_INTEGER, 89 | 90 | /* interpolations */ 91 | TOKEN_COMM_INTERP, 92 | TOKEN_DESC_INTERP, 93 | 94 | /* mods */ 95 | TOKEN_MOD_CTRL, 96 | TOKEN_MOD_ALT, 97 | TOKEN_MOD_HYPER, 98 | TOKEN_MOD_SHIFT, 99 | 100 | /* keys */ 101 | TOKEN_KEY, 102 | TOKEN_SPECIAL_KEY, 103 | 104 | /* control */ 105 | TOKEN_NO_INTERP, 106 | TOKEN_EMPTY, 107 | TOKEN_ERROR, 108 | TOKEN_EOF, 109 | } TokenType; 110 | 111 | typedef struct 112 | { 113 | TokenType type; 114 | const char* start; 115 | size_t length; 116 | uint32_t line; 117 | uint32_t column; 118 | const char* message; 119 | size_t messageLength; 120 | } Token; 121 | 122 | typedef struct 123 | { 124 | Token* tokens; 125 | size_t count; 126 | size_t capacity; 127 | } TokenArray; 128 | 129 | void copyToken(const Token* from, Token* to); 130 | void copyTokenArray(const TokenArray* from, TokenArray* to); 131 | void errorAtToken(const Token* token, const char* filepath, const char* fmt, ...); 132 | bool getDoubleFromToken(const Token* token, double* dest, bool debug); 133 | bool getInt32FromToken(const Token* token, int32_t* dest, bool debug); 134 | bool getUint32FromToken(const Token* token, uint32_t* dest, bool debug); 135 | const char* getTokenLiteral(const TokenType type); 136 | void initToken(Token* token); 137 | void initTokenArray(TokenArray* tokens); 138 | bool isTokenHookType(const TokenType type); 139 | bool isTokenModType(const TokenType type); 140 | void freeTokenArray(TokenArray* tokens); 141 | void writeTokenArray(TokenArray* tokens, Token* token); 142 | 143 | #endif /* WK_COMPILER_TOKEN_H_ */ 144 | -------------------------------------------------------------------------------- /src/compiler/writer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* common includes */ 7 | #include "common/key_chord.h" 8 | 9 | /* local includes */ 10 | #include "writer.h" 11 | 12 | static void writeKeyChords(const KeyChord* keyChords, int indent); 13 | 14 | static void 15 | writeIndent(int indent) 16 | { 17 | printf("%*s", indent * 4, " "); 18 | } 19 | 20 | static void 21 | writeNewlineWithIndent(int indent) 22 | { 23 | printf("\n%*s", indent * 4, " "); 24 | } 25 | 26 | static void 27 | writeChordsHeader(void) 28 | { 29 | printf( 30 | "#ifndef WK_CONFIG_KEY_CHORDS_H_\n" 31 | "#define WK_CONFIG_KEY_CHORDS_H_\n" 32 | "\n" 33 | "#include \n" 34 | "\n" 35 | "#include \"src/common/key_chord.h\"\n" 36 | "\n" 37 | "/* state,\n" 38 | " * KEY(\n" 39 | " * mods,\n" 40 | " * special,\n" 41 | " * key, key_len\n" 42 | " * ),\n" 43 | " * description,\n" 44 | " * command\n" 45 | " * before\n" 46 | " * after\n" 47 | " * flags, chords\n" 48 | " */\n" 49 | "KeyChord builtinKeyChords[] = {\n" 50 | ); 51 | } 52 | 53 | static void 54 | writeChordsFooter(void) 55 | { 56 | printf( 57 | "};\n" 58 | "\n" 59 | "#endif /* WK_CONFIG_KEY_CHORDS_H_ */\n" 60 | ); 61 | } 62 | 63 | static void 64 | writeChordMods(const Modifiers* mods, int indent) 65 | { 66 | assert(mods); 67 | 68 | printf(".mods = {"); 69 | writeNewlineWithIndent(indent + 1); 70 | printf( 71 | ".ctrl = %-5s, .alt = %-5s, .hyper = %-5s, .shift = %-5s", 72 | (mods->ctrl ? "true" : "false"), 73 | (mods->alt ? "true" : "false"), 74 | (mods->hyper ? "true" : "false"), 75 | (mods->shift ? "true" : "false") 76 | ); 77 | writeNewlineWithIndent(indent); 78 | printf("},"); 79 | } 80 | 81 | static void 82 | writeChordSpecial(SpecialKey special) 83 | { 84 | printf(" .special = %s,", getSpecialKeyLiteral(special)); 85 | } 86 | 87 | static void 88 | writeEscString(const char* text) 89 | { 90 | if (!text) 91 | { 92 | printf("NULL, "); 93 | return; 94 | } 95 | 96 | printf("\""); 97 | const char* current = text; 98 | while (*current != '\0') 99 | { 100 | switch (*current) 101 | { 102 | case '\\': printf("\\"); break; 103 | case '\"': printf("\\\""); break; 104 | case '\n': printf("\\\n"); break; 105 | default: printf("%c", *current); break; 106 | } 107 | current++; 108 | } 109 | printf("\", "); 110 | } 111 | 112 | static void 113 | writeEscStringWithIndent(const char* member, const char* text, int indent) 114 | { 115 | writeIndent(indent); 116 | printf(".%s = ", member); 117 | writeEscString(text); 118 | } 119 | 120 | static void 121 | writeEscStringWithIndentAndNewline(const char* member, const char* text, int indent) 122 | { 123 | printf("\n"); 124 | writeEscStringWithIndent(member, text, indent); 125 | } 126 | 127 | static void 128 | writeChordKey(const Key* key, int indent) 129 | { 130 | assert(key); 131 | 132 | writeNewlineWithIndent(indent); 133 | printf(".key = {"); 134 | writeNewlineWithIndent(indent + 1); 135 | writeChordMods(&key->mods, indent + 1); 136 | writeNewlineWithIndent(indent); 137 | writeChordSpecial(key->special); 138 | printf("\n "); 139 | writeEscStringWithIndent("repr", key->repr, indent); 140 | printf(".len = %lu", strlen(key->repr)); 141 | writeNewlineWithIndent(indent); 142 | printf("},\n"); 143 | } 144 | 145 | static void 146 | writeChordFlags(const ChordFlags* flags, int indent) 147 | { 148 | assert(flags); 149 | 150 | printf(".flags = {\n"); 151 | writeIndent(indent); 152 | printf( 153 | " %-5s, %-5s, %-5s, %-5s, %-5s, %-5s, %-5s,\n", 154 | (flags->keep ? "true" : "false"), 155 | (flags->close ? "true" : "false"), 156 | (flags->inherit ? "true" : "false"), 157 | (flags->ignore ? "true" : "false"), 158 | (flags->ignoreSort ? "true" : "false"), 159 | (flags->unhook ? "true" : "false"), 160 | (flags->deflag ? "true" : "false") 161 | ); 162 | writeIndent(indent); 163 | printf(" %-5s, %-5s, %-5s, %-5s, %-5s, %-5s, %-5s\n", 164 | (flags->nobefore ? "true" : "false"), 165 | (flags->noafter ? "true" : "false"), 166 | (flags->write ? "true" : "false"), 167 | (flags->execute ? "true" : "false"), 168 | (flags->syncCommand ? "true" : "false"), 169 | (flags->syncBefore ? "true" : "false"), 170 | (flags->syncAfter ? "true" : "false") 171 | ); 172 | writeIndent(indent); 173 | printf("}, "); 174 | } 175 | 176 | static void 177 | writeChord(const KeyChord* keyChord, int indent) 178 | { 179 | assert(keyChord); 180 | 181 | printf("%*s", indent * 4, " "); 182 | printf(".state = KEY_CHORD_STATE_NOT_NULL, "); 183 | writeChordKey(&keyChord->key, indent); 184 | writeEscStringWithIndent("description", keyChord->description, indent); 185 | 186 | /* command */ 187 | writeEscStringWithIndentAndNewline("command", keyChord->command, indent); 188 | 189 | /* before */ 190 | writeEscStringWithIndentAndNewline("before", keyChord->before, indent); 191 | 192 | /* after */ 193 | writeEscStringWithIndentAndNewline("after", keyChord->after, indent); 194 | 195 | /* flags */ 196 | writeNewlineWithIndent(indent); 197 | writeChordFlags(&keyChord->flags, indent); 198 | 199 | /* prefix */ 200 | if (keyChord->keyChords) 201 | { 202 | writeNewlineWithIndent(indent); 203 | printf(".keyChords = (KeyChord[]){\n"); 204 | writeKeyChords(keyChord->keyChords, indent + 1); 205 | writeIndent(indent); 206 | printf("}\n"); 207 | } 208 | else 209 | { 210 | printf(".keyChords = NULL\n"); 211 | } 212 | } 213 | 214 | static void 215 | writeNullKeyChord(int indent) 216 | { 217 | writeIndent(indent); 218 | printf("{ .state = KEY_CHORD_STATE_IS_NULL }\n"); 219 | } 220 | 221 | static void 222 | writeKeyChords(const KeyChord* keyChords, int indent) 223 | { 224 | assert(keyChords); 225 | 226 | size_t count = countKeyChords(keyChords); 227 | for (size_t i = 0; i < count; i++) 228 | { 229 | writeIndent(indent); 230 | printf("{\n"); 231 | writeChord(&keyChords[i], indent + 1); 232 | writeIndent(indent); 233 | printf("},\n"); 234 | } 235 | 236 | writeNullKeyChord(indent); 237 | } 238 | 239 | void 240 | writeBuiltinKeyChordsHeaderFile(const KeyChord* keyChords) 241 | { 242 | assert(keyChords); 243 | 244 | writeChordsHeader(); 245 | writeKeyChords(keyChords, 1); 246 | writeChordsFooter(); 247 | } 248 | -------------------------------------------------------------------------------- /src/compiler/writer.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_COMPILER_WRITER_H_ 2 | #define WK_COMPILER_WRITER_H_ 3 | 4 | /* common includes */ 5 | #include "common/key_chord.h" 6 | 7 | void writeBuiltinKeyChordsHeaderFile(const KeyChord* keyChords); 8 | 9 | #endif /* WK_COMPILER_WRITER_H_ */ 10 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | /* config includes */ 8 | #include "config/key_chords.h" 9 | 10 | /* common includes */ 11 | #include "common/common.h" 12 | #include "common/debug.h" 13 | #include "common/menu.h" 14 | #include "common/string.h" 15 | #include "common/key_chord.h" 16 | 17 | /* compiler includes */ 18 | #include "compiler/common.h" 19 | #include "compiler/compiler.h" 20 | #include "compiler/preprocessor.h" 21 | #include "compiler/writer.h" 22 | 23 | static void 24 | freeKeyChords(KeyChord* keyChords) 25 | { 26 | if (!keyChords) return; 27 | 28 | for (size_t i = 0; keyChords[i].state == KEY_CHORD_STATE_NOT_NULL; i++) 29 | { 30 | free(keyChords[i].key.repr); 31 | free(keyChords[i].description); 32 | free(keyChords[i].command); 33 | free(keyChords[i].before); 34 | free(keyChords[i].after); 35 | if (keyChords[i].keyChords) freeKeyChords(keyChords[i].keyChords); 36 | } 37 | free(keyChords); 38 | } 39 | 40 | static char* 41 | preprocessSource(Menu* menu, const char* source, const char* filepath) 42 | { 43 | assert(menu), assert(source); 44 | 45 | char* processedSource = runPreprocessor(menu, source, filepath); 46 | if (!processedSource) 47 | { 48 | errorMsg("Failed while running preprocessor on `wks` file: '%s'.", filepath); 49 | } 50 | else if (menu->debug) 51 | { 52 | debugPrintHeader(" Contents of Preprocessed Source "); 53 | debugMsg(true, "| "); 54 | debugTextWithLineNumber(processedSource); 55 | debugMsg(true, "| "); 56 | debugPrintHeader(""); 57 | } 58 | 59 | return processedSource; 60 | } 61 | 62 | static int 63 | compileSource(Menu* menu, Compiler* compiler, char* source, const char* filepath) 64 | { 65 | assert(menu),assert(compiler), assert(source), assert(filepath); 66 | 67 | initCompiler(menu, compiler, source, filepath); 68 | 69 | /* Compile lines, retruns null on error. */ 70 | menu->keyChordsHead = menu->keyChords = compileKeyChords(compiler, menu); 71 | if (!menu->keyChords) return EX_DATAERR; 72 | 73 | return EX_OK; 74 | } 75 | 76 | static int 77 | runMenu(Menu* menu) 78 | { 79 | assert(menu); 80 | 81 | int result = EX_SOFTWARE; 82 | MenuStatus status = MENU_STATUS_RUNNING; 83 | countMenuKeyChords(menu); 84 | 85 | /* Pre-press keys */ 86 | if (menu->client.keys) 87 | { 88 | if (menu->debug) debugMsg(true, "Trying to press key(s): '%s'.", menu->client.keys); 89 | status = pressKeys(menu, menu->client.keys); 90 | } 91 | 92 | /* If keys were pre-pressed there may be nothing to do, or an error to report. */ 93 | if (status == MENU_STATUS_EXIT_SOFTWARE) 94 | { 95 | errorMsg("Key(s) not found in key chords: '%s'.", menu->client.keys); 96 | result = EX_DATAERR; 97 | } 98 | else if (status == MENU_STATUS_EXIT_OK) 99 | { 100 | debugMsg(menu->debug, "Successfully pressed keys: '%s'.", menu->client.keys); 101 | result = EX_OK; 102 | } 103 | else 104 | { 105 | result = displayMenu(menu); 106 | } 107 | return result; 108 | } 109 | 110 | static int 111 | runSource(Menu* menu, const char* source, const char* filepath) 112 | { 113 | assert(menu), assert(source), assert(filepath); 114 | 115 | int result = EX_SOFTWARE; 116 | 117 | /* Run preprocessor on `script` and fail if mallformed or other error. */ 118 | char* processedSource = preprocessSource(menu, source, filepath); 119 | if (!processedSource) return EX_DATAERR; 120 | 121 | /* Begin compilation */ 122 | Compiler compiler = {0}; 123 | result = compileSource(menu, &compiler, processedSource, filepath); 124 | if (result != EX_OK) 125 | { 126 | errorMsg("Could not compile `wks` file: '%s'.", filepath); 127 | goto fail; 128 | } 129 | 130 | result = runMenu(menu); 131 | 132 | freeKeyChords(menu->keyChordsHead); 133 | fail: 134 | free(processedSource); 135 | return result; 136 | } 137 | 138 | /* Read the given '.wks' file, and transpile it into chords.h syntax. */ 139 | static int 140 | transpileWksFile(Menu* menu) 141 | { 142 | assert(menu); 143 | 144 | int result = EX_SOFTWARE; 145 | 146 | /* Read given file to `source` and exit if read failed. */ 147 | char* source = readFile(menu->client.transpile); 148 | if (!source) return EX_IOERR; 149 | 150 | /* Run the preprocessor on source */ 151 | char* processedSource = preprocessSource(menu, source, menu->client.transpile); 152 | if (!processedSource) 153 | { 154 | result = EX_DATAERR; 155 | goto end; 156 | } 157 | 158 | Compiler compiler = {0}; 159 | result = compileSource(menu, &compiler, processedSource, menu->client.transpile); 160 | if (result != EX_OK) goto fail; 161 | 162 | /* Well formed file, write to stdout. */ 163 | writeBuiltinKeyChordsHeaderFile(menu->keyChordsHead); 164 | 165 | freeKeyChords(menu->keyChordsHead); 166 | fail: 167 | free(processedSource); 168 | end: 169 | free(source); 170 | return result; 171 | } 172 | 173 | /* Read stdin as though it were a '.wks' file, 174 | * compile it into a Chord array, and execute like normal. 175 | */ 176 | static int 177 | runScript(Menu* menu) 178 | { 179 | assert(menu); 180 | 181 | int result = EX_SOFTWARE; 182 | 183 | /* Exit on failure to read stdin. */ 184 | if (!tryStdin(menu)) return EX_IOERR; 185 | String* source = &menu->client.script; 186 | 187 | result = runSource(menu, source->string, "."); 188 | 189 | freeString(source); 190 | return result; 191 | } 192 | 193 | /* Read the given '.wks' file, compile it into a Chord array, and execute like normal. */ 194 | static int 195 | runWksFile(Menu* menu) 196 | { 197 | assert(menu); 198 | 199 | int result = EX_SOFTWARE; 200 | 201 | /* Exit on failure to read source file. */ 202 | char* source = readFile(menu->client.wksFile); 203 | if (!source) return EX_IOERR; 204 | 205 | result = runSource(menu, source, menu->client.wksFile); 206 | 207 | free(source); 208 | return result; 209 | } 210 | 211 | /* Execute precompiled key chords. */ 212 | static int 213 | runBuiltinKeyChords(Menu* menu) 214 | { 215 | assert(menu); 216 | 217 | if (menu->debug) disassembleKeyChords(menu->keyChords, 0); 218 | return runMenu(menu); 219 | } 220 | 221 | int 222 | main(int argc, char** argv) 223 | { 224 | int result = EX_SOFTWARE; 225 | 226 | Menu menu = {0}; 227 | initMenu(&menu, builtinKeyChords); 228 | parseArgs(&menu, &argc, &argv); 229 | 230 | if (menu.debug) disassembleMenu(&menu); 231 | 232 | if (menu.client.transpile) 233 | { 234 | result = transpileWksFile(&menu); 235 | } 236 | else if (menu.client.tryScript) 237 | { 238 | result = runScript(&menu); 239 | } 240 | else if (menu.client.wksFile) 241 | { 242 | result = runWksFile(&menu); 243 | } 244 | else 245 | { 246 | result = runBuiltinKeyChords(&menu); 247 | } 248 | 249 | freeMenuGarbage(&menu); 250 | 251 | return result; 252 | } 253 | -------------------------------------------------------------------------------- /src/runtime/cairo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | /* Cairo and pango includes */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | /* common includes */ 18 | #include "common/common.h" 19 | #include "common/debug.h" 20 | #include "common/menu.h" 21 | #include "common/key_chord.h" 22 | 23 | /* local includes */ 24 | #include "cairo.h" 25 | 26 | static Cairo* cairo; 27 | static Menu* mainMenu; 28 | static uint32_t width; 29 | static uint32_t height; 30 | static int ellipsisWidth = -1; 31 | static int ellipsisHeight = -1; 32 | static bool ellipsisIsSet = false; 33 | 34 | bool 35 | cairoCreateForSurface(Cairo* cairo, cairo_surface_t* surface) 36 | { 37 | assert(cairo), assert(surface); 38 | 39 | cairo->cr = cairo_create(surface); 40 | if (!cairo->cr) goto fail; 41 | 42 | cairo->surface = surface; 43 | assert(cairo->scale > 0); 44 | cairo_surface_set_device_scale(surface, cairo->scale, cairo->scale); 45 | return true; 46 | 47 | fail: 48 | if (cairo->cr) cairo_destroy(cairo->cr); 49 | return false; 50 | } 51 | 52 | void 53 | cairoDestroy(Cairo* cairo) 54 | { 55 | assert(cairo); 56 | 57 | if (cairo->cr) cairo_destroy(cairo->cr); 58 | if (cairo->surface) cairo_surface_destroy(cairo->surface); 59 | } 60 | 61 | static void 62 | calculateGrid(const uint32_t count, const uint32_t maxCols, uint32_t* rows, uint32_t* cols) 63 | { 64 | assert(rows), assert(cols); 65 | 66 | if (maxCols == 0 || maxCols >= count) 67 | { 68 | *rows = 1; 69 | *cols = count; 70 | } 71 | else 72 | { 73 | *rows = (count + maxCols - 1) / maxCols; 74 | *cols = (count + *rows - 1) / *rows; 75 | } 76 | } 77 | 78 | uint32_t 79 | cairoGetHeight(Menu* menu, cairo_surface_t* surface, uint32_t maxHeight) 80 | { 81 | assert(menu), assert(surface); 82 | 83 | uint32_t height = 0; 84 | countMenuKeyChords(menu); 85 | 86 | cairo_t* cr = cairo_create(surface); 87 | PangoLayout* layout = pango_cairo_create_layout(cr); 88 | PangoFontDescription* fontDesc = pango_font_description_from_string(menu->font); 89 | PangoRectangle rect; 90 | 91 | calculateGrid(menu->keyChordCount, menu->maxCols, &menu->rows, &menu->cols); 92 | 93 | pango_layout_set_text( 94 | layout, 95 | "!\"#$%%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" 96 | "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", 97 | -1 98 | ); 99 | pango_layout_set_font_description(layout, fontDesc); 100 | pango_layout_set_single_paragraph_mode(layout, 1); 101 | pango_layout_get_pixel_extents(layout, NULL, &rect); 102 | 103 | /* cleanup */ 104 | pango_font_description_free(fontDesc); 105 | g_object_unref(layout); 106 | cairo_destroy(cr); 107 | cairo_surface_destroy(surface); 108 | 109 | menu->cellHeight = (rect.height + menu->hpadding * 2); 110 | height = menu->cellHeight * menu->rows + (menu->borderWidth * 2); 111 | return height > maxHeight ? maxHeight : height; 112 | } 113 | 114 | static void 115 | cairoSetColors(CairoPaint* paint, MenuHexColor* colors) 116 | { 117 | assert(paint), assert(colors); 118 | 119 | /* foreground - key */ 120 | paint->fgKey.r = (float)colors[MENU_COLOR_KEY].r / 255.0f; 121 | paint->fgKey.g = (float)colors[MENU_COLOR_KEY].g / 255.0f; 122 | paint->fgKey.b = (float)colors[MENU_COLOR_KEY].b / 255.0f; 123 | paint->fgKey.a = (float)colors[MENU_COLOR_KEY].a / 255.0f; 124 | 125 | /* foreground - delimiter */ 126 | paint->fgDelimiter.r = (float)colors[MENU_COLOR_DELIMITER].r / 255.0f; 127 | paint->fgDelimiter.g = (float)colors[MENU_COLOR_DELIMITER].g / 255.0f; 128 | paint->fgDelimiter.b = (float)colors[MENU_COLOR_DELIMITER].b / 255.0f; 129 | paint->fgDelimiter.a = (float)colors[MENU_COLOR_DELIMITER].a / 255.0f; 130 | 131 | /* foreground - prefix */ 132 | paint->fgPrefix.r = (float)colors[MENU_COLOR_PREFIX].r / 255.0f; 133 | paint->fgPrefix.g = (float)colors[MENU_COLOR_PREFIX].g / 255.0f; 134 | paint->fgPrefix.b = (float)colors[MENU_COLOR_PREFIX].b / 255.0f; 135 | paint->fgPrefix.a = (float)colors[MENU_COLOR_PREFIX].a / 255.0f; 136 | 137 | /* foreground - chord */ 138 | paint->fgChord.r = (float)colors[MENU_COLOR_CHORD].r / 255.0f; 139 | paint->fgChord.g = (float)colors[MENU_COLOR_CHORD].g / 255.0f; 140 | paint->fgChord.b = (float)colors[MENU_COLOR_CHORD].b / 255.0f; 141 | paint->fgChord.a = (float)colors[MENU_COLOR_CHORD].a / 255.0f; 142 | 143 | /* background */ 144 | paint->bg.r = (float)colors[MENU_COLOR_BACKGROUND].r / 255.0f; 145 | paint->bg.g = (float)colors[MENU_COLOR_BACKGROUND].g / 255.0f; 146 | paint->bg.b = (float)colors[MENU_COLOR_BACKGROUND].b / 255.0f; 147 | paint->bg.a = (float)colors[MENU_COLOR_BACKGROUND].a / 255.0f; 148 | 149 | /* border */ 150 | paint->bd.r = (float)colors[MENU_COLOR_BORDER].r / 255.0f; 151 | paint->bd.g = (float)colors[MENU_COLOR_BORDER].g / 255.0f; 152 | paint->bd.b = (float)colors[MENU_COLOR_BORDER].b / 255.0f; 153 | paint->bd.a = (float)colors[MENU_COLOR_BORDER].a / 255.0f; 154 | } 155 | 156 | void 157 | cairoInitPaint(Menu* menu, CairoPaint* paint) 158 | { 159 | assert(menu), assert(paint); 160 | 161 | cairoSetColors(paint, menu->colors); 162 | paint->font = menu->font; 163 | } 164 | 165 | static bool 166 | setSourceRgba(MenuColor type) 167 | { 168 | CairoColor* color = NULL; 169 | 170 | switch (type) 171 | { 172 | case MENU_COLOR_KEY: color = &cairo->paint->fgKey; break; 173 | case MENU_COLOR_DELIMITER: color = &cairo->paint->fgDelimiter; break; 174 | case MENU_COLOR_PREFIX: color = &cairo->paint->fgPrefix; break; 175 | case MENU_COLOR_CHORD: color = &cairo->paint->fgChord; break; 176 | case MENU_COLOR_BACKGROUND: color = &cairo->paint->bg; break; 177 | case MENU_COLOR_BORDER: color = &cairo->paint->bd; break; 178 | default: errorMsg("Invalid color request %d", type); return false; 179 | } 180 | 181 | cairo_set_source_rgba(cairo->cr, color->r, color->g, color->b, color->a); 182 | return true; 183 | } 184 | 185 | static void 186 | cairoDrawRoundedPath(double radius) 187 | { 188 | cairo_t* cr = cairo->cr; 189 | double degrees = M_PI / 180; 190 | double x = mainMenu->borderWidth / 2.0; 191 | double y = mainMenu->borderWidth / 2.0; 192 | double w = width - mainMenu->borderWidth; 193 | double h = height - mainMenu->borderWidth; 194 | cairo_new_sub_path(cr); 195 | cairo_arc(cr, x + w - radius, y + radius, radius, -90 * degrees, 0 * degrees); 196 | cairo_arc(cr, x + w - radius, y + h - radius, radius, 0 * degrees, 90 * degrees); 197 | cairo_arc(cr, x + radius, y + h - radius, radius, 90 * degrees, 180 * degrees); 198 | cairo_arc(cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees); 199 | cairo_close_path(cr); 200 | } 201 | 202 | static bool 203 | drawBackground() 204 | { 205 | assert(cairo), assert(mainMenu); 206 | 207 | if (!setSourceRgba(MENU_COLOR_BACKGROUND)) return false; 208 | 209 | double radius = mainMenu->borderRadius; 210 | 211 | if (!radius) { 212 | cairo_paint(cairo->cr); 213 | } else { 214 | cairoDrawRoundedPath(radius); 215 | cairo_fill(cairo->cr); 216 | } 217 | 218 | return true; 219 | } 220 | 221 | static bool 222 | drawBorder() 223 | { 224 | assert(cairo), assert(mainMenu); 225 | 226 | double lineW = cairo_get_line_width(cairo->cr); 227 | cairo_set_line_width(cairo->cr, mainMenu->borderWidth); 228 | if (!setSourceRgba(MENU_COLOR_BORDER)) return false; 229 | 230 | double radius = mainMenu->borderRadius; 231 | 232 | if (!radius) { 233 | double x = mainMenu->borderWidth / 2.0; 234 | double y = mainMenu->borderWidth / 2.0; 235 | double w = width - mainMenu->borderWidth; 236 | double h = height - mainMenu->borderWidth; 237 | cairo_rectangle(cairo->cr, x, y, w, h); 238 | } else { 239 | cairoDrawRoundedPath(radius); 240 | } 241 | 242 | cairo_stroke(cairo->cr); 243 | cairo_set_line_width(cairo->cr, lineW); 244 | return true; 245 | } 246 | 247 | static void 248 | drawTruncatedText(PangoLayout* layout, const char* text, uint32_t cellw) 249 | { 250 | assert(layout), assert(text); 251 | if ((uint32_t)ellipsisWidth > cellw) return; 252 | 253 | size_t len = strlen(text); 254 | int textw; 255 | int texth; 256 | uint32_t truncatedWidth = 0; 257 | char buffer[len + 1]; /* +1 for null byte '\0' */ 258 | memcpy(buffer, text, len + 1); /* +1 to copy null byte '\0' */ 259 | 260 | pango_layout_set_text(layout, buffer, len); 261 | pango_layout_get_pixel_size(layout, &textw, &texth); 262 | 263 | truncatedWidth = textw + ellipsisWidth; 264 | if (truncatedWidth <= cellw) return; 265 | 266 | size_t left = 0; 267 | size_t right = len; 268 | while (left < right) 269 | { 270 | while (left < right && isUtf8ContByte(buffer[left])) left++; 271 | size_t mid = (left + right) / 2; 272 | while (mid > left && !isUtf8StartByte(buffer[mid])) mid--; 273 | 274 | pango_layout_set_text(layout, buffer, mid); 275 | pango_layout_get_pixel_size(layout, &textw, &texth); 276 | 277 | truncatedWidth = textw + ellipsisWidth; 278 | if (truncatedWidth < cellw) 279 | { 280 | left = mid + 1; 281 | } 282 | else if (truncatedWidth == cellw) 283 | { 284 | left = mid; 285 | break; 286 | } 287 | else 288 | { 289 | right = mid; 290 | } 291 | } 292 | 293 | len = truncatedWidth == cellw ? left : left - 1; 294 | if (truncatedWidth != cellw && len) 295 | { 296 | while (len && !isUtf8StartByte(buffer[len])) len--; 297 | } 298 | 299 | memcpy(buffer + len, "...", 4); 300 | pango_layout_set_text(layout, buffer, -1); 301 | } 302 | 303 | static bool 304 | drawText(PangoLayout* layout, const char* text, uint32_t* cellw, uint32_t* x, uint32_t* y) 305 | { 306 | assert(layout), assert(text), assert(cellw), assert(x), assert(y); 307 | if (*cellw == 0) return false; 308 | if ((uint32_t)ellipsisWidth > *cellw) return false; 309 | 310 | int w, h; 311 | pango_layout_set_text(layout, text, -1); 312 | pango_layout_get_pixel_size(layout, &w, &h); 313 | 314 | if ((uint32_t)w > *cellw) 315 | { 316 | drawTruncatedText(layout, text, *cellw); 317 | *cellw = 0; 318 | } 319 | else 320 | { 321 | *cellw -= w; 322 | } 323 | 324 | cairo_move_to(cairo->cr, *x, *y); 325 | pango_cairo_show_layout(cairo->cr, layout); 326 | *x += w; 327 | return *cellw != 0; 328 | } 329 | 330 | static bool 331 | drawModText(PangoLayout* layout, uint32_t idx, uint32_t* cellw, uint32_t* x, uint32_t* y) 332 | { 333 | assert(layout), assert(cellw), assert(x), assert(y); 334 | if (!setSourceRgba(MENU_COLOR_KEY)) return false; 335 | 336 | Modifiers* mods = &mainMenu->keyChords[idx].key.mods; 337 | if (mods->ctrl && !drawText(layout, "C-", cellw, x, y)) return false; 338 | if (mods->alt && !drawText(layout, "M-", cellw, x, y)) return false; 339 | if (mods->hyper && !drawText(layout, "H-", cellw, x, y)) return false; 340 | if (mods->shift && !drawText(layout, "S-", cellw, x, y)) return false; 341 | 342 | return true; 343 | } 344 | 345 | static bool 346 | drawKeyText(PangoLayout* layout, uint32_t idx, uint32_t* cellw, uint32_t* x, uint32_t* y) 347 | { 348 | assert(layout), assert(cellw), assert(x), assert(y); 349 | if (!setSourceRgba(MENU_COLOR_KEY)) return false; 350 | 351 | return drawText(layout, mainMenu->keyChords[idx].key.repr, cellw, x, y); 352 | } 353 | 354 | static bool 355 | drawDelimiterText(PangoLayout* layout, uint32_t* cellw, uint32_t* x, uint32_t* y) 356 | { 357 | assert(layout), assert(cellw), assert(x), assert(y); 358 | if (!setSourceRgba(MENU_COLOR_DELIMITER)) return false; 359 | 360 | return drawText(layout, mainMenu->delimiter, cellw, x, y); 361 | } 362 | 363 | static bool 364 | drawDescriptionText(PangoLayout* layout, uint32_t idx, uint32_t* cellw, uint32_t* x, uint32_t* y) 365 | { 366 | assert(layout), assert(cellw), assert(x), assert(y); 367 | if (!setSourceRgba( 368 | mainMenu->keyChords[idx].keyChords ? MENU_COLOR_PREFIX : MENU_COLOR_CHORD 369 | )) return false; 370 | 371 | return drawText(layout, mainMenu->keyChords[idx].description, cellw, x, y); 372 | } 373 | 374 | static void 375 | drawHintText(PangoLayout* layout, uint32_t idx, uint32_t cellw, uint32_t x, uint32_t y) 376 | { 377 | assert(layout); 378 | 379 | if (!drawModText(layout, idx, &cellw, &x, &y)) return; 380 | if (!drawKeyText(layout, idx, &cellw, &x, &y)) return; 381 | if (!drawDelimiterText(layout, &cellw, &x, &y)) return; 382 | if (!drawDescriptionText(layout, idx, &cellw, &x, &y)) return; 383 | } 384 | 385 | static bool 386 | drawGrid() 387 | { 388 | assert(cairo), assert(mainMenu); 389 | 390 | if (mainMenu->borderWidth * 2 >= width) 391 | { 392 | errorMsg("Border is larger than menu width."); 393 | goto end; 394 | } 395 | 396 | uint32_t startx = mainMenu->borderWidth; 397 | uint32_t starty = mainMenu->borderWidth; 398 | uint32_t rows = mainMenu->rows; 399 | uint32_t cols = mainMenu->cols; 400 | uint32_t wpadding = mainMenu->wpadding; 401 | uint32_t hpadding = mainMenu->hpadding; 402 | uint32_t cellWidth = (width - (mainMenu->borderWidth * 2)) / cols; 403 | uint32_t cellHeight = mainMenu->cellHeight; 404 | uint32_t idx = 0; 405 | uint32_t count = mainMenu->keyChordCount; 406 | PangoLayout* layout = pango_cairo_create_layout(cairo->cr); 407 | PangoFontDescription* fontDesc = pango_font_description_from_string(mainMenu->font); 408 | 409 | pango_layout_set_font_description(layout, fontDesc); 410 | pango_font_description_free(fontDesc); 411 | 412 | if (!setSourceRgba(MENU_COLOR_KEY)) goto fail; 413 | 414 | if (mainMenu->debug) 415 | { 416 | disassembleGrid(startx, starty, rows, cols, wpadding, hpadding, cellWidth, cellHeight, count); 417 | disassembleKeyChordsShallow(mainMenu->keyChords, mainMenu->keyChordCount); 418 | } 419 | 420 | if (!ellipsisIsSet) 421 | { 422 | ellipsisIsSet = true; 423 | pango_layout_set_text(layout, "...", -1); 424 | pango_layout_get_pixel_size(layout, &ellipsisWidth, &ellipsisHeight); 425 | } 426 | 427 | if ((wpadding * 2) >= cellWidth) 428 | { 429 | errorMsg("Width padding is larger than cell size. Unable to draw anything."); 430 | goto fail; 431 | } 432 | 433 | if ((uint32_t)ellipsisWidth > cellWidth - (wpadding * 2)) 434 | { 435 | warnMsg("Not enough cell space to draw truncated hints."); 436 | ellipsisWidth = 0; 437 | ellipsisHeight = -1; 438 | } 439 | 440 | for (uint32_t i = 0; i < cols && idx < count; i++) 441 | { 442 | uint32_t x = startx + (i * cellWidth) + wpadding; 443 | for (uint32_t j = 0; j < rows && idx < count; j++, idx++) 444 | { 445 | uint32_t y = starty + (j * cellHeight) + hpadding; 446 | drawHintText(layout, idx, cellWidth - (wpadding * 2), x, y); 447 | } 448 | } 449 | 450 | g_object_unref(layout); 451 | return true; 452 | 453 | fail: 454 | g_object_unref(layout); 455 | end: 456 | return false; 457 | } 458 | 459 | bool 460 | cairoPaint(Cairo* cr, Menu* menu) 461 | { 462 | assert(cr), assert(menu); 463 | 464 | if (menuIsDelayed(menu)) return true; 465 | 466 | if (menu->debug) disassembleMenu(menu); 467 | 468 | cairo = cr; 469 | mainMenu = menu; 470 | width = menu->width; 471 | height = menu->height; 472 | 473 | height /= cairo->scale; 474 | 475 | if (!drawBackground()) 476 | { 477 | errorMsg("Could not draw background."); 478 | goto fail; 479 | } 480 | 481 | if (!drawBorder()) 482 | { 483 | errorMsg("Could not draw border."); 484 | goto fail; 485 | } 486 | 487 | if (!drawGrid()) 488 | { 489 | errorMsg("Could not draw grid."); 490 | goto fail; 491 | } 492 | 493 | return true; 494 | 495 | fail: 496 | return false; 497 | } 498 | -------------------------------------------------------------------------------- /src/runtime/cairo.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_RUNTIME_CAIRO_H_ 2 | #define WK_RUNTIME_CAIRO_H_ 3 | 4 | #include 5 | 6 | #include "common/menu.h" 7 | 8 | typedef struct 9 | { 10 | float r, g, b, a; 11 | } CairoColor; 12 | 13 | typedef struct 14 | { 15 | CairoColor fgKey; 16 | CairoColor fgDelimiter; 17 | CairoColor fgPrefix; 18 | CairoColor fgChord; 19 | CairoColor bg; 20 | CairoColor bd; 21 | const char* font; 22 | } CairoPaint; 23 | 24 | typedef struct 25 | { 26 | cairo_t* cr; 27 | cairo_surface_t* surface; 28 | CairoPaint* paint; 29 | uint32_t scale; 30 | uint32_t width; 31 | uint32_t height; 32 | } Cairo; 33 | 34 | bool cairoCreateForSurface(Cairo* cairo, cairo_surface_t* surface); 35 | void cairoDestroy(Cairo* cairo); 36 | uint32_t cairoGetHeight(Menu* menu, cairo_surface_t* surface, uint32_t maxHeight); 37 | void cairoInitPaint(Menu* menu, CairoPaint* paint); 38 | bool cairoPaint(Cairo* cairo, Menu* menu); 39 | 40 | #endif /* WK_RUNTIME_CAIRO_H_ */ 41 | -------------------------------------------------------------------------------- /src/runtime/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /* common includes */ 7 | #include "common/common.h" 8 | #include "common/key_chord.h" 9 | 10 | /* local includes */ 11 | #include "common.h" 12 | 13 | bool 14 | isNormalKey(Key* key, char* buffer, size_t len) 15 | { 16 | assert(key), assert(buffer); 17 | if (!isUtf8MultiByteStartByte(*buffer) && iscntrl(*buffer)) return false; 18 | 19 | key->repr = buffer; 20 | key->len = len; 21 | 22 | return true; 23 | } 24 | 25 | bool 26 | isSpecialKey(Key* key, void* keysym, GetSpecialKeyFp fp) 27 | { 28 | assert(key), assert(keysym); 29 | 30 | key->special = fp(keysym); 31 | if (key->special == SPECIAL_KEY_NONE) return false; 32 | 33 | key->repr = (char*)getSpecialKeyRepr(key->special); 34 | key->len = strlen(key->repr); 35 | 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /src/runtime/common.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_RUNTIME_COMMON_H_ 2 | #define WK_RUNTIME_COMMON_H_ 3 | 4 | #include 5 | #include 6 | 7 | /* common includes */ 8 | #include "common/key_chord.h" 9 | 10 | typedef SpecialKey (*GetSpecialKeyFp)(void*); 11 | 12 | bool isNormalKey(Key* key, char* buffer, size_t len); 13 | bool isSpecialKey(Key* key, void* keysym, GetSpecialKeyFp fp); 14 | 15 | #endif /* WK_RUNTIME_COMMON_H_ */ 16 | -------------------------------------------------------------------------------- /src/runtime/debug.c: -------------------------------------------------------------------------------- 1 | /* common headers */ 2 | #include "common/debug.h" 3 | 4 | /* local header */ 5 | #include "debug.h" 6 | 7 | void 8 | disassembleCairoPaint(const CairoPaint* paint) 9 | { 10 | /* FOREGROUND - key*/ 11 | debugMsgWithIndent(0, "|---- Foreground Key Value ----"); 12 | debugMsgWithIndent(0, "|"); 13 | debugMsgWithIndent(0, "| Red: %#02X", (uint8_t)(paint->fgKey.r * 255)); 14 | debugMsgWithIndent(0, "| Green: %#02X", (uint8_t)(paint->fgKey.g * 255)); 15 | debugMsgWithIndent(0, "| Blue: %#02X", (uint8_t)(paint->fgKey.b * 255)); 16 | debugMsgWithIndent(0, "| Alpha: %#02X", (uint8_t)(paint->fgKey.a * 255)); 17 | debugMsgWithIndent(0, "|"); 18 | 19 | /* FOREGROUND - delimiter */ 20 | debugMsgWithIndent(0, "|--- Foreground Delim Value ---"); 21 | debugMsgWithIndent(0, "|"); 22 | debugMsgWithIndent(0, "| Red: %#02X", (uint8_t)(paint->fgDelimiter.r * 255)); 23 | debugMsgWithIndent(0, "| Green: %#02X", (uint8_t)(paint->fgDelimiter.g * 255)); 24 | debugMsgWithIndent(0, "| Blue: %#02X", (uint8_t)(paint->fgDelimiter.b * 255)); 25 | debugMsgWithIndent(0, "| Alpha: %#02X", (uint8_t)(paint->fgDelimiter.a * 255)); 26 | debugMsgWithIndent(0, "|"); 27 | 28 | /* FOREGROUND - prefix */ 29 | debugMsgWithIndent(0, "|-- Foreground Prefix Value ---"); 30 | debugMsgWithIndent(0, "|"); 31 | debugMsgWithIndent(0, "| Red: %#02X", (uint8_t)(paint->fgPrefix.r * 255)); 32 | debugMsgWithIndent(0, "| Green: %#02X", (uint8_t)(paint->fgPrefix.g * 255)); 33 | debugMsgWithIndent(0, "| Blue: %#02X", (uint8_t)(paint->fgPrefix.b * 255)); 34 | debugMsgWithIndent(0, "| Alpha: %#02X", (uint8_t)(paint->fgPrefix.a * 255)); 35 | debugMsgWithIndent(0, "|"); 36 | 37 | /* FOREGROUND - chord */ 38 | debugMsgWithIndent(0, "|--- Foreground Chord Value ---"); 39 | debugMsgWithIndent(0, "|"); 40 | debugMsgWithIndent(0, "| Red: %#02X", (uint8_t)(paint->fgChord.r * 255)); 41 | debugMsgWithIndent(0, "| Green: %#02X", (uint8_t)(paint->fgChord.g * 255)); 42 | debugMsgWithIndent(0, "| Blue: %#02X", (uint8_t)(paint->fgChord.b * 255)); 43 | debugMsgWithIndent(0, "| Alpha: %#02X", (uint8_t)(paint->fgChord.a * 255)); 44 | debugMsgWithIndent(0, "|"); 45 | 46 | debugMsgWithIndent(0, "|------ Background value ------"); 47 | debugMsgWithIndent(0, "|"); 48 | debugMsgWithIndent(0, "| Red: %#02X", (uint8_t)(paint->bg.r * 255)); 49 | debugMsgWithIndent(0, "| Green: %#02X", (uint8_t)(paint->bg.g * 255)); 50 | debugMsgWithIndent(0, "| Blue: %#02X", (uint8_t)(paint->bg.b * 255)); 51 | debugMsgWithIndent(0, "| Alpha: %#02X", (uint8_t)(paint->bg.a * 255)); 52 | debugMsgWithIndent(0, "|"); 53 | 54 | debugMsgWithIndent(0, "|-------- Border value --------"); 55 | debugMsgWithIndent(0, "|"); 56 | debugMsgWithIndent(0, "| Red: %#02X", (uint8_t)(paint->bd.r * 255)); 57 | debugMsgWithIndent(0, "| Green: %#02X", (uint8_t)(paint->bd.g * 255)); 58 | debugMsgWithIndent(0, "| Blue: %#02X", (uint8_t)(paint->bd.b * 255)); 59 | debugMsgWithIndent(0, "| Alpha: %#02X", (uint8_t)(paint->bd.a * 255)); 60 | } 61 | -------------------------------------------------------------------------------- /src/runtime/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_RUNTIME_DEBUG_H_ 2 | #define WK_RUNTIME_DEBUG_H_ 3 | 4 | #include "cairo.h" 5 | 6 | void disassembleCairoPaint(const CairoPaint* paint); 7 | 8 | #endif /* WK_RUNTIME_DEBUG_H_ */ 9 | -------------------------------------------------------------------------------- /src/runtime/wayland/debug.c: -------------------------------------------------------------------------------- 1 | /* common includes */ 2 | #include "common/debug.h" 3 | 4 | /* runtime includes */ 5 | #include "runtime/debug.h" 6 | 7 | /* local includes */ 8 | #include "debug.h" 9 | #include "window.h" 10 | 11 | void 12 | disassembleWaylandWindow(WaylandWindow* window) 13 | { 14 | debugPrintHeader(" WkWaylandWindow "); 15 | debugMsgWithIndent(0, "|"); 16 | debugMsgWithIndent(0, "| Window width: %04u", window->width); 17 | debugMsgWithIndent(0, "| Window max width: %04u", window->maxWidth); 18 | debugMsgWithIndent(0, "| Window height: %04u", window->height); 19 | debugMsgWithIndent(0, "| Window max height: %04u", window->maxHeight); 20 | debugMsgWithIndent(0, "|"); 21 | disassembleCairoPaint(&window->paint); 22 | debugMsgWithIndent(0, "|"); 23 | debugPrintHeader(""); 24 | } 25 | -------------------------------------------------------------------------------- /src/runtime/wayland/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_WAYLAND_DEBUG_H_ 2 | #define WK_WAYLAND_DEBUG_H_ 3 | 4 | #include "window.h" 5 | 6 | void disassembleWaylandWindow(WaylandWindow* window); 7 | 8 | #endif /* WK_WAYLAND_DEBUG_H_ */ 9 | -------------------------------------------------------------------------------- /src/runtime/wayland/registry.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_WAYLAND_REGISTRY_H_ 2 | #define WK_WAYLAND_REGISTRY_H_ 3 | 4 | #include 5 | 6 | /* common includes */ 7 | #include "common/menu.h" 8 | 9 | typedef struct Wayland Wayland; 10 | 11 | typedef enum 12 | { 13 | MOD_SHIFT = 1<<0, 14 | MOD_CAPS = 1<<1, 15 | MOD_CTRL = 1<<2, 16 | MOD_ALT = 1<<3, 17 | MOD_MOD2 = 1<<4, 18 | MOD_MOD3 = 1<<5, 19 | MOD_LOGO = 1<<6, 20 | MOD_MOD5 = 1<<7, 21 | } XkbModBit; 22 | 23 | typedef enum 24 | { 25 | MASK_SHIFT, 26 | MASK_CAPS, 27 | MASK_CTRL, 28 | MASK_ALT, 29 | MASK_MOD2, 30 | MASK_MOD3, 31 | MASK_LOGO, 32 | MASK_MOD5, 33 | MASK_LAST, 34 | } XkbModMask; 35 | 36 | extern const char* WK_XKB_MASK_NAMES[MASK_LAST]; 37 | extern const XkbModBit WK_XKB_MODS[MASK_LAST]; 38 | 39 | void waylandRepeat(Wayland* wayland); 40 | void waylandRegistryDestroy(Wayland* wayland); 41 | bool waylandRegistryRegister(Wayland* wayland, Menu* props); 42 | 43 | #endif /* WK_WAYLAND_REGISTRY_H_ */ 44 | -------------------------------------------------------------------------------- /src/runtime/wayland/wayland.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_WAYLAND_WAYLAND_H_ 2 | #define WK_WAYLAND_WAYLAND_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /* common includes */ 9 | #include "common/menu.h" 10 | 11 | /* local includes */ 12 | #include "registry.h" 13 | 14 | typedef enum 15 | { 16 | TOUCH_EVENT_DOWN = (1 << 0), 17 | TOUCH_EVENT_UP = (1 << 1), 18 | TOUCH_EVENT_MOTION = (1 << 2), 19 | TOUCH_EVENT_CANCEL = (1 << 3), 20 | TOUCH_EVENT_SHAPE = (1 << 4), 21 | TOUCH_EVENT_ORIENTATION = (1 << 5), 22 | } TouchEventMask; 23 | 24 | typedef enum 25 | { 26 | POINTER_EVENT_ENTER = (1 << 0), 27 | POINTER_EVENT_LEAVE = (1 << 1), 28 | POINTER_EVENT_MOTION = (1 << 2), 29 | POINTER_EVENT_BUTTON = (1 << 3), 30 | POINTER_EVENT_AXIS = (1 << 4), 31 | POINTER_EVENT_AXIS_SOURCE = (1 << 5), 32 | POINTER_EVENT_AXIS_STOP = (1 << 6), 33 | POINTER_EVENT_AXIS_DISCRETE = (1 << 7), 34 | } PointerEventMask; 35 | 36 | typedef struct 37 | { 38 | struct xkb_state* state; 39 | struct xkb_context* context; 40 | struct xkb_keymap* keymap; 41 | xkb_mod_mask_t masks[MASK_LAST]; 42 | uint32_t depressedMods; 43 | uint32_t latchedMods; 44 | uint32_t lockedMods; 45 | uint32_t group; 46 | } Xkb; 47 | 48 | typedef struct 49 | { 50 | uint32_t eventMask; 51 | wl_fixed_t surfaceX; 52 | wl_fixed_t surfaceY; 53 | uint32_t button; 54 | uint32_t state; 55 | uint32_t time; 56 | uint32_t serial; 57 | struct 58 | { 59 | bool valid; 60 | wl_fixed_t value; 61 | int32_t discrete; 62 | } axes[2]; 63 | uint32_t axisSource; 64 | } PointerEvent; 65 | 66 | typedef struct 67 | { 68 | bool valid; 69 | int32_t id; 70 | uint32_t eventMask; 71 | wl_fixed_t surfaceX; 72 | wl_fixed_t surfaceY; 73 | wl_fixed_t surfaceStartX; 74 | wl_fixed_t surfaceStartY; 75 | wl_fixed_t major; 76 | wl_fixed_t minor; 77 | wl_fixed_t orientation; 78 | } TouchPoint; 79 | 80 | typedef struct 81 | { 82 | uint32_t time; 83 | uint32_t serial; 84 | uint16_t active; 85 | TouchPoint points[2]; 86 | } TouchEvent; 87 | 88 | typedef struct 89 | { 90 | int* repeatFd; 91 | 92 | struct wl_seat* seat; 93 | struct wl_keyboard* keyboard; 94 | struct wl_pointer* pointer; 95 | struct wl_touch* touch; 96 | PointerEvent pointerEvent; 97 | TouchEvent touchEvent; 98 | Xkb xkb; 99 | 100 | xkb_keysym_t keysym; 101 | uint32_t code; 102 | uint32_t modifiers; 103 | 104 | xkb_keysym_t repeatKeysym; 105 | uint32_t repeatKey; 106 | 107 | struct 108 | { 109 | int32_t sec; 110 | int32_t nsec; 111 | } repeatRate; 112 | 113 | struct 114 | { 115 | int32_t sec; 116 | int32_t nsec; 117 | } repeatDelay; 118 | 119 | struct 120 | { 121 | void (*key)(enum wl_keyboard_key_state state, xkb_keysym_t keysym, uint32_t code); 122 | } notify; 123 | 124 | bool keyPending; 125 | } Input; 126 | 127 | typedef struct 128 | { 129 | struct wl_output* output; 130 | struct wl_list link; 131 | uint32_t width; 132 | uint32_t height; 133 | int scale; 134 | char* name; 135 | } Output; 136 | 137 | typedef struct 138 | { 139 | Output* output; 140 | struct wl_list link; 141 | } SurfaceOutput; 142 | 143 | typedef struct Wayland 144 | { 145 | struct 146 | { 147 | int32_t display; 148 | int32_t repeat; 149 | } fds; 150 | 151 | struct wl_display* display; 152 | struct wl_registry* registry; 153 | struct wl_compositor* compositor; 154 | struct wl_list outputs; 155 | Output* selectedOutput; 156 | struct wl_seat* seat; 157 | struct zwlr_layer_shell_v1* layerShell; 158 | struct wl_shm* shm; 159 | Input input; 160 | struct wl_list windows; 161 | uint32_t formats; 162 | } Wayland; 163 | 164 | void freeWayland(Wayland* wayland); 165 | bool initWayland(Menu* props, Wayland* wayland); 166 | int runWayland(Menu* props); 167 | 168 | #endif /* WK_WAYLAND_WAYLAND_H_ */ 169 | -------------------------------------------------------------------------------- /src/runtime/wayland/window.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* common includes */ 15 | #include "common/common.h" 16 | #include "common/memory.h" 17 | #include "common/menu.h" 18 | 19 | /* runtime includes */ 20 | #include "runtime/cairo.h" 21 | 22 | /* local includes */ 23 | #include "wayland.h" 24 | #include "debug.h" 25 | #include "window.h" 26 | #include "wlr-layer-shell-unstable-v1.h" 27 | 28 | static int 29 | setCloexecOrClose(int fd) 30 | { 31 | if (fd == -1) return -1; 32 | 33 | long flags = fcntl(fd, F_GETFD); 34 | if (flags == -1) goto error; 35 | if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) goto error; 36 | 37 | return fd; 38 | 39 | error: 40 | close(fd); 41 | return -1; 42 | } 43 | 44 | static int 45 | createTmpfileCloexec(char* tmpName) 46 | { 47 | assert(tmpName); 48 | 49 | int fd; 50 | 51 | #ifdef HAVE_MKOSTEMP 52 | if ((fd = mkostemp(tmpName, O_CLOEXEC)) >= 0) unlink(tmpName); 53 | #else 54 | if ((fd = mkstemp(tmpName)) >= 0) 55 | { 56 | fd = setCloexecOrClose(fd); 57 | unlink(tmpName); 58 | } 59 | #endif 60 | 61 | return fd; 62 | } 63 | 64 | static int 65 | osCreateAnonymousFile(off_t size) 66 | { 67 | static const char template[] = "wk-shared-XXXXXX"; 68 | int fd; 69 | int result; 70 | 71 | const char* path = getenv("XDG_RUNTIME_DIR"); 72 | if (!path || strlen(path) <= 0) 73 | { 74 | errno = ENOENT; 75 | return -1; 76 | } 77 | 78 | char* ts = (path[strlen(path) - 1] == '/') ? "" : "/" ; 79 | size_t len = snprintf(NULL, 0, "%s%s%s", path, ts, template) + 1; /* +1 for null byte '\0' */ 80 | char* name = ALLOCATE(char, len); 81 | if (!name) return -1; 82 | snprintf(name, len, "%s%s%s", path, ts, template); 83 | 84 | fd = createTmpfileCloexec(name); 85 | free(name); 86 | 87 | if (fd < 0) return -1; 88 | 89 | #ifdef HAVE_POSIX_FOLLOCATE 90 | if ((result = posix_follocate(fd, 0, size)) != 0) 91 | { 92 | close(fd); 93 | errno = result; 94 | return -1; 95 | } 96 | #else 97 | if ((result = ftruncate(fd, size)) < 0) 98 | { 99 | close(fd); 100 | return -1; 101 | } 102 | #endif 103 | 104 | return fd; 105 | } 106 | 107 | static void 108 | bufferRelease(void* data, struct wl_buffer* wlBuffer) 109 | { 110 | (void)wlBuffer; 111 | Buffer* buffer = data; 112 | buffer->busy = false; 113 | } 114 | 115 | static const struct wl_buffer_listener bufferListener = { 116 | .release = bufferRelease, 117 | }; 118 | 119 | static void 120 | destroyBuffer(Buffer* buffer) 121 | { 122 | if (buffer->buffer) wl_buffer_destroy(buffer->buffer); 123 | cairoDestroy(&buffer->cairo); 124 | memset(buffer, 0, sizeof(Buffer)); 125 | } 126 | 127 | static bool 128 | createBuffer( 129 | struct wl_shm* shm, 130 | Buffer* buffer, 131 | int32_t width, 132 | int32_t height, 133 | uint32_t format, 134 | int32_t scale, 135 | CairoPaint* paint) 136 | { 137 | assert(shm), assert(buffer), assert(paint); 138 | 139 | uint32_t stride = width * 4; 140 | uint32_t size = stride * height; 141 | int fd = osCreateAnonymousFile(size); 142 | 143 | if (fd < 0) 144 | { 145 | errorMsg("Wayland: Creating a buffer file for %d B failed.", size); 146 | return false; 147 | } 148 | 149 | void* data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 150 | if (data == MAP_FAILED) 151 | { 152 | errorMsg("Wayland: mmap failed."); 153 | close(fd); 154 | return false; 155 | } 156 | 157 | struct wl_shm_pool* pool = wl_shm_create_pool(shm, fd, size); 158 | if (!pool) 159 | { 160 | errorMsg("Wayland: wl_shm_create_pool failed."); 161 | close(fd); 162 | return false; 163 | } 164 | 165 | buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format); 166 | if (!buffer->buffer) goto fail; 167 | 168 | wl_shm_pool_destroy(pool); 169 | pool = NULL; 170 | 171 | close(fd); 172 | fd = -1; 173 | 174 | wl_buffer_add_listener(buffer->buffer, &bufferListener, buffer); 175 | 176 | cairo_surface_t* surface = cairo_image_surface_create_for_data( 177 | data, CAIRO_FORMAT_ARGB32, width, height, stride 178 | ); 179 | if (!surface) goto fail; 180 | 181 | buffer->cairo.scale = scale; 182 | 183 | if (!cairoCreateForSurface(&buffer->cairo, surface)) 184 | { 185 | cairo_surface_destroy(surface); 186 | goto fail; 187 | } 188 | 189 | buffer->cairo.paint = paint; 190 | buffer->width = width; 191 | buffer->height = height; 192 | return true; 193 | 194 | fail: 195 | if (fd > -1) close(fd); 196 | if (pool) wl_shm_pool_destroy(pool); 197 | destroyBuffer(buffer); 198 | return false; 199 | } 200 | 201 | static Buffer* 202 | nextBuffer(WaylandWindow* window) 203 | { 204 | assert(window); 205 | 206 | Buffer* buffer = NULL; 207 | for (size_t i = 0; i < 2; i++) 208 | { 209 | if (window->buffers[i].busy) continue; 210 | 211 | buffer = &window->buffers[i]; 212 | break; 213 | } 214 | 215 | if (!buffer) return NULL; 216 | 217 | if (window->width * window->scale != buffer->width || 218 | window->height * window->scale != buffer->height) 219 | { 220 | destroyBuffer(buffer); 221 | } 222 | 223 | if (!buffer->buffer && 224 | !createBuffer( 225 | window->shm, buffer, window->width * window->scale, window->height * window->scale, 226 | WL_SHM_FORMAT_ARGB8888, window->scale, &window->paint 227 | )) 228 | { 229 | return NULL; 230 | } 231 | 232 | wl_surface_set_buffer_scale(window->surface, window->scale); 233 | 234 | return buffer; 235 | } 236 | 237 | static void 238 | frameCallback(void* data, struct wl_callback* callback, uint32_t time) 239 | { 240 | (void)time; 241 | WaylandWindow* window = data; 242 | wl_callback_destroy(callback); 243 | window->framecb = NULL; 244 | window->renderPending = true; 245 | } 246 | 247 | static const struct wl_callback_listener callbackListener = { 248 | frameCallback, 249 | }; 250 | 251 | static uint32_t 252 | getAlignAnchor(MenuPosition position) 253 | { 254 | uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; 255 | 256 | if (position == MENU_POS_BOTTOM) 257 | { 258 | anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; 259 | } 260 | else 261 | { 262 | anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; 263 | } 264 | 265 | return anchor; 266 | } 267 | 268 | void 269 | windowScheduleRender(WaylandWindow* window) 270 | { 271 | assert(window); 272 | 273 | if (window->framecb) return; 274 | 275 | window->framecb = wl_surface_frame(window->surface); 276 | wl_callback_add_listener(window->framecb, &callbackListener, window); 277 | wl_surface_commit(window->surface); 278 | } 279 | 280 | static cairo_surface_t* 281 | getThrowawaySurface(WaylandWindow* window) 282 | { 283 | assert(window); 284 | 285 | return cairo_image_surface_create( 286 | CAIRO_FORMAT_ARGB32, window->width * window->scale, window->height * window->scale 287 | ); 288 | } 289 | 290 | static void 291 | resizeWinWidth(WaylandWindow* window, Menu* menu) 292 | { 293 | assert(window), assert(menu); 294 | 295 | int32_t windowWidth = menu->menuWidth; 296 | uint32_t outputWidth = window->maxWidth; 297 | 298 | if (windowWidth < 0) 299 | { 300 | /* set width to half the size of the output */ 301 | window->width = outputWidth / 2; 302 | } 303 | else if (windowWidth == 0 || (uint32_t)windowWidth > outputWidth) 304 | { 305 | /* make the window as wide as the output */ 306 | window->width = outputWidth; 307 | } 308 | else 309 | { 310 | /* set the width to the desired user setting */ 311 | window->width = windowWidth; 312 | } 313 | } 314 | 315 | static void 316 | resizeWinHeight(WaylandWindow* window, Menu* menu) 317 | { 318 | assert(window), assert(menu); 319 | 320 | uint32_t outputHeight = window->maxHeight; 321 | 322 | if (window->height >= outputHeight) 323 | { 324 | /* set the height to the size of the output */ 325 | window->windowGap = 0; 326 | window->height = outputHeight; 327 | } 328 | } 329 | 330 | static void 331 | resizeWinGap(WaylandWindow* window, Menu* menu) 332 | { 333 | assert(window), assert(menu); 334 | 335 | int32_t windowGap = menu->menuGap; 336 | uint32_t outputHeight = window->maxHeight; 337 | 338 | if (windowGap < 0) 339 | { 340 | /* set gap to 1/10th the size of the output */ 341 | window->windowGap = (outputHeight / 10); 342 | } 343 | /* else if (windowGap == 0 || (uint32_t)windowGap > output->height) */ 344 | else if ((uint32_t)windowGap > outputHeight) 345 | { 346 | /* make the window as large as possible */ 347 | window->windowGap = outputHeight - window->height; 348 | } 349 | else 350 | { 351 | /* make the gap as large as the user wants */ 352 | window->windowGap = windowGap; 353 | } 354 | } 355 | 356 | 357 | static void 358 | resizeWindow(WaylandWindow* window, Menu* menu) 359 | { 360 | assert(window), assert(menu); 361 | 362 | window->height = cairoGetHeight(menu, getThrowawaySurface(window), window->maxHeight); 363 | resizeWinWidth(window, menu); 364 | resizeWinHeight(window, menu); 365 | resizeWinGap(window, menu); 366 | } 367 | 368 | static void 369 | moveResizeWindow(WaylandWindow* window, struct wl_display* display) 370 | { 371 | assert(window); 372 | 373 | zwlr_layer_surface_v1_set_size( 374 | window->layerSurface, window->width * window->scale, window->height * window->scale 375 | ); 376 | zwlr_layer_surface_v1_set_anchor(window->layerSurface, window->alignAnchor); 377 | if (window->position == MENU_POS_BOTTOM) 378 | { 379 | zwlr_layer_surface_v1_set_margin( 380 | window->layerSurface, 0, 0, window->windowGap * window->scale, 0 381 | ); 382 | } 383 | else 384 | { 385 | zwlr_layer_surface_v1_set_margin( 386 | window->layerSurface, window->windowGap * window->scale, 0, 0, 0 387 | ); 388 | } 389 | wl_surface_commit(window->surface); 390 | wl_display_roundtrip(display); 391 | } 392 | 393 | bool 394 | windowRender(WaylandWindow* window, struct wl_display* display, Menu* menu) 395 | { 396 | assert(window), assert(menu); 397 | 398 | resizeWindow(window, menu); 399 | 400 | if (menu->debug) disassembleWaylandWindow(window); 401 | 402 | Buffer* buffer = nextBuffer(window); 403 | if (!buffer) 404 | { 405 | errorMsg("Could not get buffer while rendering."); 406 | return false; 407 | } 408 | 409 | menu->width = buffer->width; 410 | menu->height = buffer->height; 411 | window->render(&buffer->cairo, menu); 412 | cairo_surface_flush(buffer->cairo.surface); 413 | 414 | moveResizeWindow(window, display); 415 | 416 | wl_surface_damage_buffer(window->surface, 0, 0, buffer->width, buffer->height); 417 | wl_surface_attach(window->surface, buffer->buffer, 0, 0); 418 | wl_surface_commit(window->surface); 419 | buffer->busy = true; 420 | window->renderPending = menuIsDelayed(menu); 421 | 422 | return true; 423 | } 424 | 425 | void 426 | windowDestroy(WaylandWindow* window) 427 | { 428 | assert(window); 429 | 430 | for (size_t i = 0; i < 2; i++) 431 | { 432 | destroyBuffer(&window->buffers[i]); 433 | } 434 | 435 | if (window->layerSurface) zwlr_layer_surface_v1_destroy(window->layerSurface); 436 | if (window->surface) wl_surface_destroy(window->surface); 437 | } 438 | 439 | static void 440 | layerSurfaceConfigure( 441 | void* data, 442 | struct zwlr_layer_surface_v1* layerSurface, 443 | uint32_t serial, 444 | uint32_t width, 445 | uint32_t height) 446 | { 447 | WaylandWindow* window = data; 448 | window->width = width; 449 | window->height = height; 450 | zwlr_layer_surface_v1_ack_configure(layerSurface, serial); 451 | } 452 | 453 | static void 454 | layerSurfaceClosed(void* data, struct zwlr_layer_surface_v1* layerSurface) 455 | { 456 | WaylandWindow* window = data; 457 | zwlr_layer_surface_v1_destroy(layerSurface); 458 | wl_surface_destroy(window->surface); 459 | exit(EX_SOFTWARE); 460 | } 461 | 462 | static const struct zwlr_layer_surface_v1_listener layerSurfaceListener = { 463 | .configure = layerSurfaceConfigure, 464 | .closed = layerSurfaceClosed, 465 | }; 466 | 467 | static uint32_t 468 | getWindowWidth(WaylandWindow* window) 469 | { 470 | assert(window); 471 | 472 | return window->width; 473 | } 474 | 475 | static uint32_t 476 | getWindowHeight(WaylandWindow* window, Menu* menu) 477 | { 478 | assert(window), assert(menu); 479 | 480 | return cairoGetHeight(menu, getThrowawaySurface(window), window->maxHeight);; 481 | } 482 | 483 | void 484 | windowGrabKeyboard(WaylandWindow* window, struct wl_display* display, bool grab) 485 | { 486 | assert(window); 487 | 488 | zwlr_layer_surface_v1_set_keyboard_interactivity(window->layerSurface, grab); 489 | wl_surface_commit(window->surface); 490 | wl_display_roundtrip(display); 491 | } 492 | 493 | void 494 | windowSetOverlap(WaylandWindow* window, struct wl_display* display, bool overlap) 495 | { 496 | assert(window); 497 | 498 | zwlr_layer_surface_v1_set_exclusive_zone(window->layerSurface, overlap ? -1 : 0); /* or ... -overlap */ 499 | wl_surface_commit(window->surface); 500 | wl_display_roundtrip(display); 501 | } 502 | 503 | bool 504 | windowCreate( 505 | WaylandWindow* window, 506 | struct wl_display* display, 507 | struct wl_shm* shm, 508 | struct wl_output* wlOutput, 509 | struct zwlr_layer_shell_v1* layerShell, 510 | struct wl_surface* surface, 511 | Menu* menu) 512 | { 513 | assert(window), assert(menu); 514 | 515 | if (!layerShell) return false; 516 | 517 | enum zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; 518 | window->layerSurface = zwlr_layer_shell_v1_get_layer_surface( 519 | layerShell, surface, wlOutput, layer, "menu" 520 | ); 521 | 522 | if (!window->layerSurface) return false; 523 | 524 | zwlr_layer_surface_v1_add_listener(window->layerSurface, &layerSurfaceListener, window); 525 | window->alignAnchor = getAlignAnchor(window->position); 526 | zwlr_layer_surface_v1_set_anchor(window->layerSurface, window->alignAnchor); 527 | zwlr_layer_surface_v1_set_size(window->layerSurface, 0, 32); 528 | 529 | wl_surface_commit(surface); 530 | wl_display_roundtrip(display); 531 | 532 | zwlr_layer_surface_v1_set_size( 533 | window->layerSurface, getWindowWidth(window), getWindowHeight(window, menu) 534 | ); 535 | 536 | window->shm = shm; 537 | window->surface = surface; 538 | 539 | cairoInitPaint(menu, &window->paint); 540 | 541 | return true; 542 | } 543 | 544 | -------------------------------------------------------------------------------- /src/runtime/wayland/window.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_WAYLAND_WINDOW_H_ 2 | #define WK_WAYLAND_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* common includes */ 10 | #include "common/menu.h" 11 | 12 | /* runtime includes */ 13 | #include "runtime/cairo.h" 14 | 15 | /* local includes */ 16 | #include "wlr-layer-shell-unstable-v1.h" 17 | 18 | typedef struct 19 | { 20 | Cairo cairo; 21 | struct wl_buffer* buffer; 22 | uint32_t width; 23 | uint32_t height; 24 | bool busy; 25 | } Buffer; 26 | 27 | typedef struct 28 | { 29 | struct Wayland* wayland; 30 | struct wl_list surfaceOutputs; 31 | struct wl_surface* surface; 32 | struct wl_callback* framecb; 33 | struct zwlr_layer_surface_v1* layerSurface; 34 | struct wl_shm* shm; 35 | Buffer buffers[2]; 36 | CairoPaint paint; 37 | uint32_t windowGap; 38 | uint32_t width; 39 | uint32_t height; 40 | uint32_t maxWidth; 41 | uint32_t maxHeight; 42 | int32_t scale; 43 | uint32_t displayed; 44 | struct wl_list link; 45 | MenuPosition position; 46 | uint32_t alignAnchor; 47 | bool renderPending; 48 | bool (*render)(Cairo* cairo, Menu* menu); 49 | } WaylandWindow; 50 | 51 | void windowScheduleRender(WaylandWindow* window); 52 | bool windowRender(WaylandWindow* window, struct wl_display* display, Menu* menu); 53 | void windowDestroy(WaylandWindow* window); 54 | void windowGrabKeyboard(WaylandWindow* window, struct wl_display* display, bool grab); 55 | void windowSetOverlap(WaylandWindow* window, struct wl_display* display, bool overlap); 56 | bool windowCreate(WaylandWindow* window, struct wl_display* display, struct wl_shm* shm, 57 | struct wl_output* wlOutput, struct zwlr_layer_shell_v1* layerShell, 58 | struct wl_surface* surface, Menu* menu); 59 | 60 | #endif /* WK_WAYLAND_WINDOW_H_ */ 61 | -------------------------------------------------------------------------------- /src/runtime/wayland/wlr-layer-shell-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2017 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 assign the surface_layer role to 31 | wl_surfaces. Such surfaces are assigned to a "layer" of the output and 32 | rendered with a defined z-depth respective to each other. They may also be 33 | anchored to the edges and corners of a screen and specify input handling 34 | semantics. This interface should be suitable for the implementation of 35 | many desktop shell components, and a broad number of other applications 36 | that interact with the desktop. 37 | 38 | 39 | 40 | 41 | Create a layer surface for an existing surface. This assigns the role of 42 | layer_surface, or raises a protocol error if another role is already 43 | assigned. 44 | 45 | Creating a layer surface from a wl_surface which has a buffer attached 46 | or committed is a client error, and any attempts by a client to attach 47 | or manipulate a buffer prior to the first layer_surface.configure call 48 | must also be treated as errors. 49 | 50 | After creating a layer_surface object and setting it up, the client 51 | must perform an initial commit without any buffer attached. 52 | The compositor will reply with a layer_surface.configure event. 53 | The client must acknowledge it and is then allowed to attach a buffer 54 | to map the surface. 55 | 56 | You may pass NULL for output to allow the compositor to decide which 57 | output to use. Generally this will be the one that the user most 58 | recently interacted with. 59 | 60 | Clients can specify a namespace that defines the purpose of the layer 61 | surface. 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | These values indicate which layers a surface can be rendered in. They 79 | are ordered by z depth, bottom-most first. Traditional shell surfaces 80 | will typically be rendered between the bottom and top layers. 81 | Fullscreen shell surfaces are typically rendered at the top layer. 82 | Multiple surfaces can share a single layer, and ordering within a 83 | single layer is undefined. 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | This request indicates that the client will not use the layer_shell 97 | object any more. Objects that have been created through this instance 98 | are not affected. 99 | 100 | 101 | 102 | 103 | 104 | 105 | An interface that may be implemented by a wl_surface, for surfaces that 106 | are designed to be rendered as a layer of a stacked desktop-like 107 | environment. 108 | 109 | Layer surface state (layer, size, anchor, exclusive zone, 110 | margin, interactivity) is double-buffered, and will be applied at the 111 | time wl_surface.commit of the corresponding wl_surface is called. 112 | 113 | Attaching a null buffer to a layer surface unmaps it. 114 | 115 | Unmapping a layer_surface means that the surface cannot be shown by the 116 | compositor until it is explicitly mapped again. The layer_surface 117 | returns to the state it had right after layer_shell.get_layer_surface. 118 | The client can re-map the surface by performing a commit without any 119 | buffer attached, waiting for a configure event and handling it as usual. 120 | 121 | 122 | 123 | 124 | Sets the size of the surface in surface-local coordinates. The 125 | compositor will display the surface centered with respect to its 126 | anchors. 127 | 128 | If you pass 0 for either value, the compositor will assign it and 129 | inform you of the assignment in the configure event. You must set your 130 | anchor to opposite edges in the dimensions you omit; not doing so is a 131 | protocol error. Both values are 0 by default. 132 | 133 | Size is double-buffered, see wl_surface.commit. 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | Requests that the compositor anchor the surface to the specified edges 142 | and corners. If two orthogonal edges are specified (e.g. 'top' and 143 | 'left'), then the anchor point will be the intersection of the edges 144 | (e.g. the top left corner of the output); otherwise the anchor point 145 | will be centered on that edge, or in the center if none is specified. 146 | 147 | Anchor is double-buffered, see wl_surface.commit. 148 | 149 | 150 | 151 | 152 | 153 | 154 | Requests that the compositor avoids occluding an area with other 155 | surfaces. The compositor's use of this information is 156 | implementation-dependent - do not assume that this region will not 157 | actually be occluded. 158 | 159 | A positive value is only meaningful if the surface is anchored to one 160 | edge or an edge and both perpendicular edges. If the surface is not 161 | anchored, anchored to only two perpendicular edges (a corner), anchored 162 | to only two parallel edges or anchored to all edges, a positive value 163 | will be treated the same as zero. 164 | 165 | A positive zone is the distance from the edge in surface-local 166 | coordinates to consider exclusive. 167 | 168 | Surfaces that do not wish to have an exclusive zone may instead specify 169 | how they should interact with surfaces that do. If set to zero, the 170 | surface indicates that it would like to be moved to avoid occluding 171 | surfaces with a positive exclusive zone. If set to -1, the surface 172 | indicates that it would not like to be moved to accommodate for other 173 | surfaces, and the compositor should extend it all the way to the edges 174 | it is anchored to. 175 | 176 | For example, a panel might set its exclusive zone to 10, so that 177 | maximized shell surfaces are not shown on top of it. A notification 178 | might set its exclusive zone to 0, so that it is moved to avoid 179 | occluding the panel, but shell surfaces are shown underneath it. A 180 | wallpaper or lock screen might set their exclusive zone to -1, so that 181 | they stretch below or over the panel. 182 | 183 | The default value is 0. 184 | 185 | Exclusive zone is double-buffered, see wl_surface.commit. 186 | 187 | 188 | 189 | 190 | 191 | 192 | Requests that the surface be placed some distance away from the anchor 193 | point on the output, in surface-local coordinates. Setting this value 194 | for edges you are not anchored to has no effect. 195 | 196 | The exclusive zone includes the margin. 197 | 198 | Margin is double-buffered, see wl_surface.commit. 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | Types of keyboard interaction possible for layer shell surfaces. The 209 | rationale for this is twofold: (1) some applications are not interested 210 | in keyboard events and not allowing them to be focused can improve the 211 | desktop experience; (2) some applications will want to take exclusive 212 | keyboard focus. 213 | 214 | 215 | 216 | 217 | This value indicates that this surface is not interested in keyboard 218 | events and the compositor should never assign it the keyboard focus. 219 | 220 | This is the default value, set for newly created layer shell surfaces. 221 | 222 | This is useful for e.g. desktop widgets that display information or 223 | only have interaction with non-keyboard input devices. 224 | 225 | 226 | 227 | 228 | Request exclusive keyboard focus if this surface is above the shell surface layer. 229 | 230 | For the top and overlay layers, the seat will always give 231 | exclusive keyboard focus to the top-most layer which has keyboard 232 | interactivity set to exclusive. If this layer contains multiple 233 | surfaces with keyboard interactivity set to exclusive, the compositor 234 | determines the one receiving keyboard events in an implementation- 235 | defined manner. In this case, no guarantee is made when this surface 236 | will receive keyboard focus (if ever). 237 | 238 | For the bottom and background layers, the compositor is allowed to use 239 | normal focus semantics. 240 | 241 | This setting is mainly intended for applications that need to ensure 242 | they receive all keyboard events, such as a lock screen or a password 243 | prompt. 244 | 245 | 246 | 247 | 248 | This requests the compositor to allow this surface to be focused and 249 | unfocused by the user in an implementation-defined manner. The user 250 | should be able to unfocus this surface even regardless of the layer 251 | it is on. 252 | 253 | Typically, the compositor will want to use its normal mechanism to 254 | manage keyboard focus between layer shell surfaces with this setting 255 | and regular toplevels on the desktop layer (e.g. click to focus). 256 | Nevertheless, it is possible for a compositor to require a special 257 | interaction to focus or unfocus layer shell surfaces (e.g. requiring 258 | a click even if focus follows the mouse normally, or providing a 259 | keybinding to switch focus between layers). 260 | 261 | This setting is mainly intended for desktop shell components (e.g. 262 | panels) that allow keyboard interaction. Using this option can allow 263 | implementing a desktop shell that can be fully usable without the 264 | mouse. 265 | 266 | 267 | 268 | 269 | 270 | 271 | Set how keyboard events are delivered to this surface. By default, 272 | layer shell surfaces do not receive keyboard events; this request can 273 | be used to change this. 274 | 275 | This setting is inherited by child surfaces set by the get_popup 276 | request. 277 | 278 | Layer surfaces receive pointer, touch, and tablet events normally. If 279 | you do not want to receive them, set the input region on your surface 280 | to an empty region. 281 | 282 | Keyboard interactivity is double-buffered, see wl_surface.commit. 283 | 284 | 285 | 286 | 287 | 288 | 289 | This assigns an xdg_popup's parent to this layer_surface. This popup 290 | should have been created via xdg_surface::get_popup with the parent set 291 | to NULL, and this request must be invoked before committing the popup's 292 | initial state. 293 | 294 | See the documentation of xdg_popup for more details about what an 295 | xdg_popup is and how it is used. 296 | 297 | 298 | 299 | 300 | 301 | 302 | When a configure event is received, if a client commits the 303 | surface in response to the configure event, then the client 304 | must make an ack_configure request sometime before the commit 305 | request, passing along the serial of the configure event. 306 | 307 | If the client receives multiple configure events before it 308 | can respond to one, it only has to ack the last configure event. 309 | 310 | A client is not required to commit immediately after sending 311 | an ack_configure request - it may even ack_configure several times 312 | before its next surface commit. 313 | 314 | A client may send multiple ack_configure requests before committing, but 315 | only the last request sent before a commit indicates which configure 316 | event the client really is responding to. 317 | 318 | 319 | 320 | 321 | 322 | 323 | This request destroys the layer surface. 324 | 325 | 326 | 327 | 328 | 329 | The configure event asks the client to resize its surface. 330 | 331 | Clients should arrange their surface for the new states, and then send 332 | an ack_configure request with the serial sent in this configure event at 333 | some point before committing the new surface. 334 | 335 | The client is free to dismiss all but the last configure event it 336 | received. 337 | 338 | The width and height arguments specify the size of the window in 339 | surface-local coordinates. 340 | 341 | The size is a hint, in the sense that the client is free to ignore it if 342 | it doesn't resize, pick a smaller size (to satisfy aspect ratio or 343 | resize in steps of NxM pixels). If the client picks a smaller size and 344 | is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the 345 | surface will be centered on this axis. 346 | 347 | If the width or height arguments are zero, it means the client should 348 | decide its own window dimension. 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | The closed event is sent by the compositor when the surface will no 358 | longer be shown. The output may have been destroyed or the user may 359 | have asked for it to be removed. Further changes to the surface will be 360 | ignored. The client should destroy the resource after receiving this 361 | event, and create a new surface if they so choose. 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | Change the layer that the surface is rendered on. 385 | 386 | Layer is double-buffered, see wl_surface.commit. 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | Requests an edge for the exclusive zone to apply. The exclusive 396 | edge will be automatically deduced from anchor points when possible, 397 | but when the surface is anchored to a corner, it will be necessary 398 | to set it explicitly to disambiguate, as it is not possible to deduce 399 | which one of the two corner edges should be used. 400 | 401 | The edge must be one the surface is anchored to, otherwise the 402 | invalid_exclusive_edge protocol error will be raised. 403 | 404 | 405 | 406 | 407 | 408 | -------------------------------------------------------------------------------- /src/runtime/x11/debug.c: -------------------------------------------------------------------------------- 1 | /* common includes */ 2 | #include "common/debug.h" 3 | 4 | /* runtime includes */ 5 | #include "runtime/debug.h" 6 | 7 | /* local includes */ 8 | #include "debug.h" 9 | #include "window.h" 10 | 11 | static void 12 | debugRootDispaly(struct display* root) 13 | { 14 | debugMsgWithIndent(0, "| Root x: %04u", root->x); 15 | debugMsgWithIndent(0, "| Root y: %04u", root->y); 16 | debugMsgWithIndent(0, "| Root width: %04u", root->w); 17 | debugMsgWithIndent(0, "| Root height: %04u", root->h); 18 | } 19 | 20 | void 21 | disassembleX11Window(X11Window* window) 22 | { 23 | debugPrintHeader(" WkX11Window "); 24 | debugMsgWithIndent(0, "|"); 25 | debugMsgWithIndent(0, "| Window x: %04u", window->x); 26 | debugMsgWithIndent(0, "| Window y: %04u", window->y); 27 | debugMsgWithIndent(0, "| Window width: %04u", window->width); 28 | debugMsgWithIndent(0, "| Window height: %04u", window->height); 29 | debugMsgWithIndent(0, "| Window border: %04u", window->border); 30 | debugMsgWithIndent(0, "| Window max height: %04u", window->maxHeight); 31 | debugRootDispaly(&window->root); 32 | debugMsgWithIndent(0, "|"); 33 | disassembleCairoPaint(&window->paint); 34 | debugMsgWithIndent(0, "|"); 35 | debugPrintHeader(""); 36 | } 37 | -------------------------------------------------------------------------------- /src/runtime/x11/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_X11_DEBUG_H_ 2 | #define WK_X11_DEBUG_H_ 3 | 4 | #include "window.h" 5 | 6 | void disassembleX11Window(X11Window* window); 7 | 8 | #endif /* WK_X11_DEBUG_H_ */ 9 | -------------------------------------------------------------------------------- /src/runtime/x11/window.h: -------------------------------------------------------------------------------- 1 | #ifndef WK_X11_WINDOW_H_ 2 | #define WK_X11_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../cairo.h" 9 | 10 | typedef struct 11 | { 12 | Cairo cairo; 13 | uint32_t width; 14 | uint32_t height; 15 | bool created; 16 | } Buffer; 17 | 18 | typedef struct 19 | { 20 | Display* display; 21 | int32_t screen; 22 | Drawable drawable; 23 | XIM xim; 24 | XIC xic; 25 | Visual* visual; 26 | KeySym keysym; 27 | uint32_t mods; 28 | Buffer buffer; 29 | uint32_t x; 30 | uint32_t y; 31 | uint32_t width; 32 | uint32_t height; 33 | uint32_t border; 34 | uint32_t maxHeight; 35 | float widthFactor; 36 | uint32_t displayed; 37 | int32_t monitor; 38 | struct display { 39 | uint32_t x, y, w, h; 40 | } root; 41 | CairoPaint paint; 42 | bool (*render)(Cairo* cairo, Menu* menu); 43 | } X11Window; 44 | 45 | typedef struct 46 | { 47 | Display* dispaly; 48 | X11Window window; 49 | Menu* menu; 50 | } X11; 51 | 52 | int runX11(Menu* menu); 53 | 54 | #endif /* WK_X11_WINDOW_H_ */ 55 | -------------------------------------------------------------------------------- /wk-which-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/3L0C/wk/b734650a9f330695a26c884d7ff5b78da94e254e/wk-which-key.png --------------------------------------------------------------------------------