feature: supporting the i3 focus command (#116)

- Added support for the i3 focus command: https://i3wm.org/docs/userguide.html#_focusing_moving_containers
- Laid down a lot of the foundation of i3 commands in general, including parsing
- CI now builds against the latest version of Mir's libraries
- Snap builds are now disabled while we wait to update to core24
This commit is contained in:
Matthew Kosarek 2024-05-02 10:14:16 -04:00 committed by GitHub
parent 460708579c
commit dd89df3214
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 6836 additions and 39 deletions

View File

@ -21,16 +21,25 @@ jobs:
- uses: actions/checkout@v3
- name: Add PPA
run: sudo apt-add-repository ppa:mir-team/release
run: |
sudo apt-add-repository ppa:mir-team/dev
sudo apt update
- name: Install dependencies
run: sudo apt-get install libmiral-dev libgtest-dev libyaml-cpp-dev libglib2.0-dev libevdev-dev nlohmann-json3-dev libnotify-dev
run: |
sudo apt update -y
sudo apt install gcc-13
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ /usr/bin/g++-13
sudo apt install libmiral-dev libmircommon-internal-dev libmircommon-dev libmirserver-internal-dev \
libgtest-dev libyaml-cpp-dev libglib2.0-dev libevdev-dev nlohmann-json3-dev libnotify-dev pcre2-utils
g++ --version
- name: Configure CMake
run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}}
- name: Build
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
- name: Test
run: cd ${{github.workspace}}/build && ./bin/miracle-wm-tests

View File

@ -12,6 +12,7 @@ concurrency:
jobs:
Snap:
if: false
runs-on: ubuntu-latest
timeout-minutes: 30
@ -33,4 +34,4 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: ${{ steps.build-snap.outputs.snap-name }}
path: ${{ steps.build-snap.outputs.snap-path }}
path: ${{ steps.build-snap.outputs.snap-path }}

View File

@ -11,6 +11,7 @@ concurrency:
jobs:
Snap:
if: false
runs-on: ubuntu-latest
timeout-minutes: 30

View File

@ -4,7 +4,7 @@ cmake_policy(SET CMP0022 NEW)
project(miracle-wm)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_COMPILER g++)
@ -18,6 +18,10 @@ option(SNAP_BUILD "Building as a snap?" OFF)
find_package(PkgConfig)
pkg_check_modules(MIRAL miral REQUIRED)
pkg_check_modules(MIRCOMMON mircommon REQUIRED)
pkg_check_modules(MIRCOMMON_INTERNAL mircommon-internal REQUIRED)
pkg_check_modules(MIRSERVER mirserver REQUIRED)
pkg_check_modules(MIRSERVER_INTERNAL mirserver-internal REQUIRED)
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-cpp)
pkg_check_modules(LIBEVDEV REQUIRED IMPORTED_TARGET libevdev)
@ -45,15 +49,32 @@ add_library(miracle-wm-implementation
src/leaf_node.cpp
src/parent_node.cpp
src/window_manager_tools_tiling_interface.cpp
src/i3_command.cpp
src/i3_command_executor.cpp
)
add_executable(miracle-wm
src/main.cpp
)
target_include_directories(miracle-wm-implementation PUBLIC SYSTEM ${MIRAL_INCLUDE_DIRS})
target_link_libraries( miracle-wm-implementation ${MIRAL_LDFLAGS}
PkgConfig::YAML PkgConfig::GLIB PkgConfig::LIBEVDEV PkgConfig::LIBNOTIFY nlohmann_json::nlohmann_json)
target_include_directories(miracle-wm-implementation PUBLIC SYSTEM
${MIRAL_INCLUDE_DIRS}
${MIRCOMMON_INCLUDE_DIRS}
${MIRCOMMON_INTERNAL_INCLUDE_DIRS}
${MIRSERVER_INTERNAL_INCLUDE_DIRS}
${MIRSERVER_INCLUDE_DIRS}
${PROJECT_SOURCE_DIR}/include)
target_link_libraries(miracle-wm-implementation
${MIRAL_LDFLAGS}
${MIRCOMMON_LDFLAGS}
${MIRSERVER_INTERNAL_LDFLAGS}
${MIRSERVER_LDFLAGS}
PkgConfig::YAML
PkgConfig::GLIB
PkgConfig::LIBEVDEV
PkgConfig::LIBNOTIFY
nlohmann_json::nlohmann_json
-lpcre2-8 -lpcre2-16 -lpcre2-32)
target_include_directories(miracle-wm PUBLIC SYSTEM ${MIRAL_INCLUDE_DIRS})
target_link_libraries(miracle-wm PUBLIC ${MIRAL_LDFLAGS} PRIVATE miracle-wm-implementation)

5153
include/jpcre2.h Normal file

File diff suppressed because it is too large Load Diff

732
include/pcre2.h Normal file
View File

@ -0,0 +1,732 @@
/*************************************************
* Perl-Compatible Regular Expressions *
*************************************************/
/* This is the public header file for the PCRE library, second API, to be
#included by applications that call PCRE2 functions.
Copyright (c) 2016 University of Cambridge
-----------------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the University of Cambridge nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-----------------------------------------------------------------------------
*/
#ifndef _PCRE2_H
#define _PCRE2_H
/* The current PCRE version information. */
#define PCRE2_MAJOR @PCRE2_MAJOR@
#define PCRE2_MINOR @PCRE2_MINOR@
#define PCRE2_PRERELEASE @PCRE2_PRERELEASE@
#define PCRE2_DATE @PCRE2_DATE@
/* When an application links to a PCRE DLL in Windows, the symbols that are
imported have to be identified as such. When building PCRE2, the appropriate
export setting is defined in pcre2_internal.h, which includes this file. So we
don't change existing definitions of PCRE2_EXP_DECL. */
#if defined(_WIN32) && !defined(PCRE2_STATIC)
# ifndef PCRE2_EXP_DECL
# define PCRE2_EXP_DECL extern __declspec(dllimport)
# endif
#endif
/* By default, we use the standard "extern" declarations. */
#ifndef PCRE2_EXP_DECL
# ifdef __cplusplus
# define PCRE2_EXP_DECL extern "C"
# else
# define PCRE2_EXP_DECL extern
# endif
#endif
/* Have to include limits.h, stdlib.h and stdint.h to ensure that size_t and
uint8_t, UCHAR_MAX, etc are defined. */
#include <limits.h>
#include <stdlib.h>
#include <stdint.h>
/* Allow for C++ users compiling this directly. */
#ifdef __cplusplus
extern "C" {
#endif
/* The following option bits can be passed to pcre2_compile(), pcre2_match(),
or pcre2_dfa_match(). PCRE2_NO_UTF_CHECK affects only the function to which it
is passed. Put these bits at the most significant end of the options word so
others can be added next to them */
#define PCRE2_ANCHORED 0x80000000u
#define PCRE2_NO_UTF_CHECK 0x40000000u
/* The following option bits can be passed only to pcre2_compile(). However,
they may affect compilation, JIT compilation, and/or interpretive execution.
The following tags indicate which:
C alters what is compiled by pcre2_compile()
J alters what is compiled by pcre2_jit_compile()
M is inspected during pcre2_match() execution
D is inspected during pcre2_dfa_match() execution
*/
#define PCRE2_ALLOW_EMPTY_CLASS 0x00000001u /* C */
#define PCRE2_ALT_BSUX 0x00000002u /* C */
#define PCRE2_AUTO_CALLOUT 0x00000004u /* C */
#define PCRE2_CASELESS 0x00000008u /* C */
#define PCRE2_DOLLAR_ENDONLY 0x00000010u /* J M D */
#define PCRE2_DOTALL 0x00000020u /* C */
#define PCRE2_DUPNAMES 0x00000040u /* C */
#define PCRE2_EXTENDED 0x00000080u /* C */
#define PCRE2_FIRSTLINE 0x00000100u /* J M D */
#define PCRE2_MATCH_UNSET_BACKREF 0x00000200u /* C J M */
#define PCRE2_MULTILINE 0x00000400u /* C */
#define PCRE2_NEVER_UCP 0x00000800u /* C */
#define PCRE2_NEVER_UTF 0x00001000u /* C */
#define PCRE2_NO_AUTO_CAPTURE 0x00002000u /* C */
#define PCRE2_NO_AUTO_POSSESS 0x00004000u /* C */
#define PCRE2_NO_DOTSTAR_ANCHOR 0x00008000u /* C */
#define PCRE2_NO_START_OPTIMIZE 0x00010000u /* J M D */
#define PCRE2_UCP 0x00020000u /* C J M D */
#define PCRE2_UNGREEDY 0x00040000u /* C */
#define PCRE2_UTF 0x00080000u /* C J M D */
#define PCRE2_NEVER_BACKSLASH_C 0x00100000u /* C */
#define PCRE2_ALT_CIRCUMFLEX 0x00200000u /* J M D */
#define PCRE2_ALT_VERBNAMES 0x00400000u /* C */
#define PCRE2_USE_OFFSET_LIMIT 0x00800000u /* J M D */
/* These are for pcre2_jit_compile(). */
#define PCRE2_JIT_COMPLETE 0x00000001u /* For full matching */
#define PCRE2_JIT_PARTIAL_SOFT 0x00000002u
#define PCRE2_JIT_PARTIAL_HARD 0x00000004u
/* These are for pcre2_match(), pcre2_dfa_match(), and pcre2_jit_match(). Note
that PCRE2_ANCHORED and PCRE2_NO_UTF_CHECK can also be passed to these
functions (though pcre2_jit_match() ignores the latter since it bypasses all
sanity checks). */
#define PCRE2_NOTBOL 0x00000001u
#define PCRE2_NOTEOL 0x00000002u
#define PCRE2_NOTEMPTY 0x00000004u /* ) These two must be kept */
#define PCRE2_NOTEMPTY_ATSTART 0x00000008u /* ) adjacent to each other. */
#define PCRE2_PARTIAL_SOFT 0x00000010u
#define PCRE2_PARTIAL_HARD 0x00000020u
/* These are additional options for pcre2_dfa_match(). */
#define PCRE2_DFA_RESTART 0x00000040u
#define PCRE2_DFA_SHORTEST 0x00000080u
/* These are additional options for pcre2_substitute(), which passes any others
through to pcre2_match(). */
#define PCRE2_SUBSTITUTE_GLOBAL 0x00000100u
#define PCRE2_SUBSTITUTE_EXTENDED 0x00000200u
#define PCRE2_SUBSTITUTE_UNSET_EMPTY 0x00000400u
#define PCRE2_SUBSTITUTE_UNKNOWN_UNSET 0x00000800u
#define PCRE2_SUBSTITUTE_OVERFLOW_LENGTH 0x00001000u
/* A further option for pcre2_match(), not allowed for pcre2_dfa_match(),
ignored for pcre2_jit_match(). */
#define PCRE2_NO_JIT 0x00002000u
/* Newline and \R settings, for use in compile contexts. The newline values
must be kept in step with values set in config.h and both sets must all be
greater than zero. */
#define PCRE2_NEWLINE_CR 1
#define PCRE2_NEWLINE_LF 2
#define PCRE2_NEWLINE_CRLF 3
#define PCRE2_NEWLINE_ANY 4
#define PCRE2_NEWLINE_ANYCRLF 5
#define PCRE2_BSR_UNICODE 1
#define PCRE2_BSR_ANYCRLF 2
/* Error codes: no match and partial match are "expected" errors. */
#define PCRE2_ERROR_NOMATCH (-1)
#define PCRE2_ERROR_PARTIAL (-2)
/* Error codes for UTF-8 validity checks */
#define PCRE2_ERROR_UTF8_ERR1 (-3)
#define PCRE2_ERROR_UTF8_ERR2 (-4)
#define PCRE2_ERROR_UTF8_ERR3 (-5)
#define PCRE2_ERROR_UTF8_ERR4 (-6)
#define PCRE2_ERROR_UTF8_ERR5 (-7)
#define PCRE2_ERROR_UTF8_ERR6 (-8)
#define PCRE2_ERROR_UTF8_ERR7 (-9)
#define PCRE2_ERROR_UTF8_ERR8 (-10)
#define PCRE2_ERROR_UTF8_ERR9 (-11)
#define PCRE2_ERROR_UTF8_ERR10 (-12)
#define PCRE2_ERROR_UTF8_ERR11 (-13)
#define PCRE2_ERROR_UTF8_ERR12 (-14)
#define PCRE2_ERROR_UTF8_ERR13 (-15)
#define PCRE2_ERROR_UTF8_ERR14 (-16)
#define PCRE2_ERROR_UTF8_ERR15 (-17)
#define PCRE2_ERROR_UTF8_ERR16 (-18)
#define PCRE2_ERROR_UTF8_ERR17 (-19)
#define PCRE2_ERROR_UTF8_ERR18 (-20)
#define PCRE2_ERROR_UTF8_ERR19 (-21)
#define PCRE2_ERROR_UTF8_ERR20 (-22)
#define PCRE2_ERROR_UTF8_ERR21 (-23)
/* Error codes for UTF-16 validity checks */
#define PCRE2_ERROR_UTF16_ERR1 (-24)
#define PCRE2_ERROR_UTF16_ERR2 (-25)
#define PCRE2_ERROR_UTF16_ERR3 (-26)
/* Error codes for UTF-32 validity checks */
#define PCRE2_ERROR_UTF32_ERR1 (-27)
#define PCRE2_ERROR_UTF32_ERR2 (-28)
/* Error codes for pcre2[_dfa]_match(), substring extraction functions, context
functions, and serializing functions. They are in numerical order. Originally
they were in alphabetical order too, but now that PCRE2 is released, the
numbers must not be changed. */
#define PCRE2_ERROR_BADDATA (-29)
#define PCRE2_ERROR_MIXEDTABLES (-30) /* Name was changed */
#define PCRE2_ERROR_BADMAGIC (-31)
#define PCRE2_ERROR_BADMODE (-32)
#define PCRE2_ERROR_BADOFFSET (-33)
#define PCRE2_ERROR_BADOPTION (-34)
#define PCRE2_ERROR_BADREPLACEMENT (-35)
#define PCRE2_ERROR_BADUTFOFFSET (-36)
#define PCRE2_ERROR_CALLOUT (-37) /* Never used by PCRE2 itself */
#define PCRE2_ERROR_DFA_BADRESTART (-38)
#define PCRE2_ERROR_DFA_RECURSE (-39)
#define PCRE2_ERROR_DFA_UCOND (-40)
#define PCRE2_ERROR_DFA_UFUNC (-41)
#define PCRE2_ERROR_DFA_UITEM (-42)
#define PCRE2_ERROR_DFA_WSSIZE (-43)
#define PCRE2_ERROR_INTERNAL (-44)
#define PCRE2_ERROR_JIT_BADOPTION (-45)
#define PCRE2_ERROR_JIT_STACKLIMIT (-46)
#define PCRE2_ERROR_MATCHLIMIT (-47)
#define PCRE2_ERROR_NOMEMORY (-48)
#define PCRE2_ERROR_NOSUBSTRING (-49)
#define PCRE2_ERROR_NOUNIQUESUBSTRING (-50)
#define PCRE2_ERROR_NULL (-51)
#define PCRE2_ERROR_RECURSELOOP (-52)
#define PCRE2_ERROR_RECURSIONLIMIT (-53)
#define PCRE2_ERROR_UNAVAILABLE (-54)
#define PCRE2_ERROR_UNSET (-55)
#define PCRE2_ERROR_BADOFFSETLIMIT (-56)
#define PCRE2_ERROR_BADREPESCAPE (-57)
#define PCRE2_ERROR_REPMISSINGBRACE (-58)
#define PCRE2_ERROR_BADSUBSTITUTION (-59)
#define PCRE2_ERROR_BADSUBSPATTERN (-60)
#define PCRE2_ERROR_TOOMANYREPLACE (-61)
#define PCRE2_ERROR_BADSERIALIZEDDATA (-62)
/* Request types for pcre2_pattern_info() */
#define PCRE2_INFO_ALLOPTIONS 0
#define PCRE2_INFO_ARGOPTIONS 1
#define PCRE2_INFO_BACKREFMAX 2
#define PCRE2_INFO_BSR 3
#define PCRE2_INFO_CAPTURECOUNT 4
#define PCRE2_INFO_FIRSTCODEUNIT 5
#define PCRE2_INFO_FIRSTCODETYPE 6
#define PCRE2_INFO_FIRSTBITMAP 7
#define PCRE2_INFO_HASCRORLF 8
#define PCRE2_INFO_JCHANGED 9
#define PCRE2_INFO_JITSIZE 10
#define PCRE2_INFO_LASTCODEUNIT 11
#define PCRE2_INFO_LASTCODETYPE 12
#define PCRE2_INFO_MATCHEMPTY 13
#define PCRE2_INFO_MATCHLIMIT 14
#define PCRE2_INFO_MAXLOOKBEHIND 15
#define PCRE2_INFO_MINLENGTH 16
#define PCRE2_INFO_NAMECOUNT 17
#define PCRE2_INFO_NAMEENTRYSIZE 18
#define PCRE2_INFO_NAMETABLE 19
#define PCRE2_INFO_NEWLINE 20
#define PCRE2_INFO_RECURSIONLIMIT 21
#define PCRE2_INFO_SIZE 22
#define PCRE2_INFO_HASBACKSLASHC 23
/* Request types for pcre2_config(). */
#define PCRE2_CONFIG_BSR 0
#define PCRE2_CONFIG_JIT 1
#define PCRE2_CONFIG_JITTARGET 2
#define PCRE2_CONFIG_LINKSIZE 3
#define PCRE2_CONFIG_MATCHLIMIT 4
#define PCRE2_CONFIG_NEWLINE 5
#define PCRE2_CONFIG_PARENSLIMIT 6
#define PCRE2_CONFIG_RECURSIONLIMIT 7
#define PCRE2_CONFIG_STACKRECURSE 8
#define PCRE2_CONFIG_UNICODE 9
#define PCRE2_CONFIG_UNICODE_VERSION 10
#define PCRE2_CONFIG_VERSION 11
/* Types for code units in patterns and subject strings. */
typedef uint8_t PCRE2_UCHAR8;
typedef uint16_t PCRE2_UCHAR16;
typedef uint32_t PCRE2_UCHAR32;
typedef const PCRE2_UCHAR8 *PCRE2_SPTR8;
typedef const PCRE2_UCHAR16 *PCRE2_SPTR16;
typedef const PCRE2_UCHAR32 *PCRE2_SPTR32;
/* The PCRE2_SIZE type is used for all string lengths and offsets in PCRE2,
including pattern offsets for errors and subject offsets after a match. We
define special values to indicate zero-terminated strings and unset offsets in
the offset vector (ovector). */
#define PCRE2_SIZE size_t
#define PCRE2_SIZE_MAX SIZE_MAX
#define PCRE2_ZERO_TERMINATED (~(PCRE2_SIZE)0)
#define PCRE2_UNSET (~(PCRE2_SIZE)0)
/* Generic types for opaque structures and JIT callback functions. These
declarations are defined in a macro that is expanded for each width later. */
#define PCRE2_TYPES_LIST \
struct pcre2_real_general_context; \
typedef struct pcre2_real_general_context pcre2_general_context; \
\
struct pcre2_real_compile_context; \
typedef struct pcre2_real_compile_context pcre2_compile_context; \
\
struct pcre2_real_match_context; \
typedef struct pcre2_real_match_context pcre2_match_context; \
\
struct pcre2_real_code; \
typedef struct pcre2_real_code pcre2_code; \
\
struct pcre2_real_match_data; \
typedef struct pcre2_real_match_data pcre2_match_data; \
\
struct pcre2_real_jit_stack; \
typedef struct pcre2_real_jit_stack pcre2_jit_stack; \
\
typedef pcre2_jit_stack *(*pcre2_jit_callback)(void *);
/* The structure for passing out data via the pcre_callout_function. We use a
structure so that new fields can be added on the end in future versions,
without changing the API of the function, thereby allowing old clients to work
without modification. Define the generic version in a macro; the width-specific
versions are generated from this macro below. */
#define PCRE2_STRUCTURE_LIST \
typedef struct pcre2_callout_block { \
uint32_t version; /* Identifies version of block */ \
/* ------------------------ Version 0 ------------------------------- */ \
uint32_t callout_number; /* Number compiled into pattern */ \
uint32_t capture_top; /* Max current capture */ \
uint32_t capture_last; /* Most recently closed capture */ \
PCRE2_SIZE *offset_vector; /* The offset vector */ \
PCRE2_SPTR mark; /* Pointer to current mark or NULL */ \
PCRE2_SPTR subject; /* The subject being matched */ \
PCRE2_SIZE subject_length; /* The length of the subject */ \
PCRE2_SIZE start_match; /* Offset to start of this match attempt */ \
PCRE2_SIZE current_position; /* Where we currently are in the subject */ \
PCRE2_SIZE pattern_position; /* Offset to next item in the pattern */ \
PCRE2_SIZE next_item_length; /* Length of next item in the pattern */ \
/* ------------------- Added for Version 1 -------------------------- */ \
PCRE2_SIZE callout_string_offset; /* Offset to string within pattern */ \
PCRE2_SIZE callout_string_length; /* Length of string compiled into pattern */ \
PCRE2_SPTR callout_string; /* String compiled into pattern */ \
/* ------------------------------------------------------------------ */ \
} pcre2_callout_block; \
\
typedef struct pcre2_callout_enumerate_block { \
uint32_t version; /* Identifies version of block */ \
/* ------------------------ Version 0 ------------------------------- */ \
PCRE2_SIZE pattern_position; /* Offset to next item in the pattern */ \
PCRE2_SIZE next_item_length; /* Length of next item in the pattern */ \
uint32_t callout_number; /* Number compiled into pattern */ \
PCRE2_SIZE callout_string_offset; /* Offset to string within pattern */ \
PCRE2_SIZE callout_string_length; /* Length of string compiled into pattern */ \
PCRE2_SPTR callout_string; /* String compiled into pattern */ \
/* ------------------------------------------------------------------ */ \
} pcre2_callout_enumerate_block;
/* List the generic forms of all other functions in macros, which will be
expanded for each width below. Start with functions that give general
information. */
#define PCRE2_GENERAL_INFO_FUNCTIONS \
PCRE2_EXP_DECL int pcre2_config(uint32_t, void *);
/* Functions for manipulating contexts. */
#define PCRE2_GENERAL_CONTEXT_FUNCTIONS \
PCRE2_EXP_DECL \
pcre2_general_context *pcre2_general_context_copy(pcre2_general_context *); \
PCRE2_EXP_DECL \
pcre2_general_context *pcre2_general_context_create( \
void *(*)(PCRE2_SIZE, void *), \
void (*)(void *, void *), void *); \
PCRE2_EXP_DECL void pcre2_general_context_free(pcre2_general_context *);
#define PCRE2_COMPILE_CONTEXT_FUNCTIONS \
PCRE2_EXP_DECL \
pcre2_compile_context *pcre2_compile_context_copy(pcre2_compile_context *); \
PCRE2_EXP_DECL \
pcre2_compile_context *pcre2_compile_context_create(pcre2_general_context *);\
PCRE2_EXP_DECL void pcre2_compile_context_free(pcre2_compile_context *); \
PCRE2_EXP_DECL int pcre2_set_bsr(pcre2_compile_context *, uint32_t); \
PCRE2_EXP_DECL int pcre2_set_character_tables(pcre2_compile_context *, \
const unsigned char *); \
PCRE2_EXP_DECL int pcre2_set_max_pattern_length(pcre2_compile_context *, \
PCRE2_SIZE); \
PCRE2_EXP_DECL int pcre2_set_newline(pcre2_compile_context *, uint32_t); \
PCRE2_EXP_DECL int pcre2_set_parens_nest_limit(pcre2_compile_context *, \
uint32_t); \
PCRE2_EXP_DECL int pcre2_set_compile_recursion_guard(\
pcre2_compile_context *, int (*)(uint32_t, void *), \
void *);
#define PCRE2_MATCH_CONTEXT_FUNCTIONS \
PCRE2_EXP_DECL \
pcre2_match_context *pcre2_match_context_copy(pcre2_match_context *); \
PCRE2_EXP_DECL \
pcre2_match_context *pcre2_match_context_create(pcre2_general_context *); \
PCRE2_EXP_DECL void pcre2_match_context_free(pcre2_match_context *); \
PCRE2_EXP_DECL int pcre2_set_callout(pcre2_match_context *, \
int (*)(pcre2_callout_block *, void *), void *); \
PCRE2_EXP_DECL int pcre2_set_match_limit(pcre2_match_context *, \
uint32_t); \
PCRE2_EXP_DECL int pcre2_set_offset_limit(pcre2_match_context *, \
PCRE2_SIZE); \
PCRE2_EXP_DECL int pcre2_set_recursion_limit(pcre2_match_context *, \
uint32_t); \
PCRE2_EXP_DECL int pcre2_set_recursion_memory_management( \
pcre2_match_context *, void *(*)(PCRE2_SIZE, void *), \
void (*)(void *, void *), void *);
/* Functions concerned with compiling a pattern to PCRE internal code. */
#define PCRE2_COMPILE_FUNCTIONS \
PCRE2_EXP_DECL \
pcre2_code *pcre2_compile(PCRE2_SPTR, PCRE2_SIZE, uint32_t, \
int *, PCRE2_SIZE *, pcre2_compile_context *); \
PCRE2_EXP_DECL void pcre2_code_free(pcre2_code *); \
PCRE2_EXP_DECL \
pcre2_code *pcre2_code_copy(const pcre2_code *);
/* Functions that give information about a compiled pattern. */
#define PCRE2_PATTERN_INFO_FUNCTIONS \
PCRE2_EXP_DECL int pcre2_pattern_info(const pcre2_code *, uint32_t, \
void *); \
PCRE2_EXP_DECL int pcre2_callout_enumerate(const pcre2_code *, \
int (*)(pcre2_callout_enumerate_block *, void *), \
void *);
/* Functions for running a match and inspecting the result. */
#define PCRE2_MATCH_FUNCTIONS \
PCRE2_EXP_DECL \
pcre2_match_data *pcre2_match_data_create(uint32_t, \
pcre2_general_context *); \
PCRE2_EXP_DECL \
pcre2_match_data *pcre2_match_data_create_from_pattern(\
const pcre2_code *, \
pcre2_general_context *); \
PCRE2_EXP_DECL int pcre2_dfa_match(const pcre2_code *, PCRE2_SPTR, \
PCRE2_SIZE, PCRE2_SIZE, uint32_t, \
pcre2_match_data *, pcre2_match_context *, int *, \
PCRE2_SIZE); \
PCRE2_EXP_DECL int pcre2_match(const pcre2_code *, \
PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, \
pcre2_match_data *, pcre2_match_context *); \
PCRE2_EXP_DECL void pcre2_match_data_free(pcre2_match_data *); \
PCRE2_EXP_DECL PCRE2_SPTR pcre2_get_mark(pcre2_match_data *); \
PCRE2_EXP_DECL uint32_t pcre2_get_ovector_count(pcre2_match_data *); \
PCRE2_EXP_DECL PCRE2_SIZE *pcre2_get_ovector_pointer(pcre2_match_data *); \
PCRE2_EXP_DECL PCRE2_SIZE pcre2_get_startchar(pcre2_match_data *);
/* Convenience functions for handling matched substrings. */
#define PCRE2_SUBSTRING_FUNCTIONS \
PCRE2_EXP_DECL int pcre2_substring_copy_byname(pcre2_match_data *, \
PCRE2_SPTR, PCRE2_UCHAR *, PCRE2_SIZE *); \
PCRE2_EXP_DECL int pcre2_substring_copy_bynumber(pcre2_match_data *, \
uint32_t, PCRE2_UCHAR *, PCRE2_SIZE *); \
PCRE2_EXP_DECL void pcre2_substring_free(PCRE2_UCHAR *); \
PCRE2_EXP_DECL int pcre2_substring_get_byname(pcre2_match_data *, \
PCRE2_SPTR, PCRE2_UCHAR **, PCRE2_SIZE *); \
PCRE2_EXP_DECL int pcre2_substring_get_bynumber(pcre2_match_data *, \
uint32_t, PCRE2_UCHAR **, PCRE2_SIZE *); \
PCRE2_EXP_DECL int pcre2_substring_length_byname(pcre2_match_data *, \
PCRE2_SPTR, PCRE2_SIZE *); \
PCRE2_EXP_DECL int pcre2_substring_length_bynumber(pcre2_match_data *, \
uint32_t, PCRE2_SIZE *); \
PCRE2_EXP_DECL int pcre2_substring_nametable_scan(const pcre2_code *, \
PCRE2_SPTR, PCRE2_SPTR *, PCRE2_SPTR *); \
PCRE2_EXP_DECL int pcre2_substring_number_from_name(\
const pcre2_code *, PCRE2_SPTR); \
PCRE2_EXP_DECL void pcre2_substring_list_free(PCRE2_SPTR *); \
PCRE2_EXP_DECL int pcre2_substring_list_get(pcre2_match_data *, \
PCRE2_UCHAR ***, PCRE2_SIZE **);
/* Functions for serializing / deserializing compiled patterns. */
#define PCRE2_SERIALIZE_FUNCTIONS \
PCRE2_EXP_DECL int32_t pcre2_serialize_encode(const pcre2_code **, \
int32_t, uint8_t **, PCRE2_SIZE *, \
pcre2_general_context *); \
PCRE2_EXP_DECL int32_t pcre2_serialize_decode(pcre2_code **, int32_t, \
const uint8_t *, pcre2_general_context *); \
PCRE2_EXP_DECL int32_t pcre2_serialize_get_number_of_codes(const uint8_t *); \
PCRE2_EXP_DECL void pcre2_serialize_free(uint8_t *);
/* Convenience function for match + substitute. */
#define PCRE2_SUBSTITUTE_FUNCTION \
PCRE2_EXP_DECL int pcre2_substitute(const pcre2_code *, \
PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, \
pcre2_match_data *, pcre2_match_context *, \
PCRE2_SPTR, PCRE2_SIZE, PCRE2_UCHAR *, \
PCRE2_SIZE *);
/* Functions for JIT processing */
#define PCRE2_JIT_FUNCTIONS \
PCRE2_EXP_DECL int pcre2_jit_compile(pcre2_code *, uint32_t); \
PCRE2_EXP_DECL int pcre2_jit_match(const pcre2_code *, \
PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, \
pcre2_match_data *, pcre2_match_context *); \
PCRE2_EXP_DECL void pcre2_jit_free_unused_memory(pcre2_general_context *); \
PCRE2_EXP_DECL \
pcre2_jit_stack *pcre2_jit_stack_create(PCRE2_SIZE, PCRE2_SIZE, \
pcre2_general_context *); \
PCRE2_EXP_DECL void pcre2_jit_stack_assign(pcre2_match_context *, \
pcre2_jit_callback, void *); \
PCRE2_EXP_DECL void pcre2_jit_stack_free(pcre2_jit_stack *);
/* Other miscellaneous functions. */
#define PCRE2_OTHER_FUNCTIONS \
PCRE2_EXP_DECL int pcre2_get_error_message(int, PCRE2_UCHAR *, PCRE2_SIZE); \
PCRE2_EXP_DECL \
const uint8_t *pcre2_maketables(pcre2_general_context *); \
/* Define macros that generate width-specific names from generic versions. The
three-level macro scheme is necessary to get the macros expanded when we want
them to be. First we get the width from PCRE2_LOCAL_WIDTH, which is used for
generating three versions of everything below. After that, PCRE2_SUFFIX will be
re-defined to use PCRE2_CODE_UNIT_WIDTH, for use when macros such as
pcre2_compile are called by application code. */
#define PCRE2_JOIN(a,b) a ## b
#define PCRE2_GLUE(a,b) PCRE2_JOIN(a,b)
#define PCRE2_SUFFIX(a) PCRE2_GLUE(a,PCRE2_LOCAL_WIDTH)
/* Data types */
#define PCRE2_UCHAR PCRE2_SUFFIX(PCRE2_UCHAR)
#define PCRE2_SPTR PCRE2_SUFFIX(PCRE2_SPTR)
#define pcre2_code PCRE2_SUFFIX(pcre2_code_)
#define pcre2_jit_callback PCRE2_SUFFIX(pcre2_jit_callback_)
#define pcre2_jit_stack PCRE2_SUFFIX(pcre2_jit_stack_)
#define pcre2_real_code PCRE2_SUFFIX(pcre2_real_code_)
#define pcre2_real_general_context PCRE2_SUFFIX(pcre2_real_general_context_)
#define pcre2_real_compile_context PCRE2_SUFFIX(pcre2_real_compile_context_)
#define pcre2_real_match_context PCRE2_SUFFIX(pcre2_real_match_context_)
#define pcre2_real_jit_stack PCRE2_SUFFIX(pcre2_real_jit_stack_)
#define pcre2_real_match_data PCRE2_SUFFIX(pcre2_real_match_data_)
/* Data blocks */
#define pcre2_callout_block PCRE2_SUFFIX(pcre2_callout_block_)
#define pcre2_callout_enumerate_block PCRE2_SUFFIX(pcre2_callout_enumerate_block_)
#define pcre2_general_context PCRE2_SUFFIX(pcre2_general_context_)
#define pcre2_compile_context PCRE2_SUFFIX(pcre2_compile_context_)
#define pcre2_match_context PCRE2_SUFFIX(pcre2_match_context_)
#define pcre2_match_data PCRE2_SUFFIX(pcre2_match_data_)
/* Functions: the complete list in alphabetical order */
#define pcre2_callout_enumerate PCRE2_SUFFIX(pcre2_callout_enumerate_)
#define pcre2_code_copy PCRE2_SUFFIX(pcre2_code_copy_)
#define pcre2_code_free PCRE2_SUFFIX(pcre2_code_free_)
#define pcre2_compile PCRE2_SUFFIX(pcre2_compile_)
#define pcre2_compile_context_copy PCRE2_SUFFIX(pcre2_compile_context_copy_)
#define pcre2_compile_context_create PCRE2_SUFFIX(pcre2_compile_context_create_)
#define pcre2_compile_context_free PCRE2_SUFFIX(pcre2_compile_context_free_)
#define pcre2_config PCRE2_SUFFIX(pcre2_config_)
#define pcre2_dfa_match PCRE2_SUFFIX(pcre2_dfa_match_)
#define pcre2_general_context_copy PCRE2_SUFFIX(pcre2_general_context_copy_)
#define pcre2_general_context_create PCRE2_SUFFIX(pcre2_general_context_create_)
#define pcre2_general_context_free PCRE2_SUFFIX(pcre2_general_context_free_)
#define pcre2_get_error_message PCRE2_SUFFIX(pcre2_get_error_message_)
#define pcre2_get_mark PCRE2_SUFFIX(pcre2_get_mark_)
#define pcre2_get_ovector_pointer PCRE2_SUFFIX(pcre2_get_ovector_pointer_)
#define pcre2_get_ovector_count PCRE2_SUFFIX(pcre2_get_ovector_count_)
#define pcre2_get_startchar PCRE2_SUFFIX(pcre2_get_startchar_)
#define pcre2_jit_compile PCRE2_SUFFIX(pcre2_jit_compile_)
#define pcre2_jit_match PCRE2_SUFFIX(pcre2_jit_match_)
#define pcre2_jit_free_unused_memory PCRE2_SUFFIX(pcre2_jit_free_unused_memory_)
#define pcre2_jit_stack_assign PCRE2_SUFFIX(pcre2_jit_stack_assign_)
#define pcre2_jit_stack_create PCRE2_SUFFIX(pcre2_jit_stack_create_)
#define pcre2_jit_stack_free PCRE2_SUFFIX(pcre2_jit_stack_free_)
#define pcre2_maketables PCRE2_SUFFIX(pcre2_maketables_)
#define pcre2_match PCRE2_SUFFIX(pcre2_match_)
#define pcre2_match_context_copy PCRE2_SUFFIX(pcre2_match_context_copy_)
#define pcre2_match_context_create PCRE2_SUFFIX(pcre2_match_context_create_)
#define pcre2_match_context_free PCRE2_SUFFIX(pcre2_match_context_free_)
#define pcre2_match_data_create PCRE2_SUFFIX(pcre2_match_data_create_)
#define pcre2_match_data_create_from_pattern PCRE2_SUFFIX(pcre2_match_data_create_from_pattern_)
#define pcre2_match_data_free PCRE2_SUFFIX(pcre2_match_data_free_)
#define pcre2_pattern_info PCRE2_SUFFIX(pcre2_pattern_info_)
#define pcre2_serialize_decode PCRE2_SUFFIX(pcre2_serialize_decode_)
#define pcre2_serialize_encode PCRE2_SUFFIX(pcre2_serialize_encode_)
#define pcre2_serialize_free PCRE2_SUFFIX(pcre2_serialize_free_)
#define pcre2_serialize_get_number_of_codes PCRE2_SUFFIX(pcre2_serialize_get_number_of_codes_)
#define pcre2_set_bsr PCRE2_SUFFIX(pcre2_set_bsr_)
#define pcre2_set_callout PCRE2_SUFFIX(pcre2_set_callout_)
#define pcre2_set_character_tables PCRE2_SUFFIX(pcre2_set_character_tables_)
#define pcre2_set_compile_recursion_guard PCRE2_SUFFIX(pcre2_set_compile_recursion_guard_)
#define pcre2_set_match_limit PCRE2_SUFFIX(pcre2_set_match_limit_)
#define pcre2_set_max_pattern_length PCRE2_SUFFIX(pcre2_set_max_pattern_length_)
#define pcre2_set_newline PCRE2_SUFFIX(pcre2_set_newline_)
#define pcre2_set_parens_nest_limit PCRE2_SUFFIX(pcre2_set_parens_nest_limit_)
#define pcre2_set_offset_limit PCRE2_SUFFIX(pcre2_set_offset_limit_)
#define pcre2_set_recursion_limit PCRE2_SUFFIX(pcre2_set_recursion_limit_)
#define pcre2_set_recursion_memory_management PCRE2_SUFFIX(pcre2_set_recursion_memory_management_)
#define pcre2_substitute PCRE2_SUFFIX(pcre2_substitute_)
#define pcre2_substring_copy_byname PCRE2_SUFFIX(pcre2_substring_copy_byname_)
#define pcre2_substring_copy_bynumber PCRE2_SUFFIX(pcre2_substring_copy_bynumber_)
#define pcre2_substring_free PCRE2_SUFFIX(pcre2_substring_free_)
#define pcre2_substring_get_byname PCRE2_SUFFIX(pcre2_substring_get_byname_)
#define pcre2_substring_get_bynumber PCRE2_SUFFIX(pcre2_substring_get_bynumber_)
#define pcre2_substring_length_byname PCRE2_SUFFIX(pcre2_substring_length_byname_)
#define pcre2_substring_length_bynumber PCRE2_SUFFIX(pcre2_substring_length_bynumber_)
#define pcre2_substring_list_get PCRE2_SUFFIX(pcre2_substring_list_get_)
#define pcre2_substring_list_free PCRE2_SUFFIX(pcre2_substring_list_free_)
#define pcre2_substring_nametable_scan PCRE2_SUFFIX(pcre2_substring_nametable_scan_)
#define pcre2_substring_number_from_name PCRE2_SUFFIX(pcre2_substring_number_from_name_)
/* Now generate all three sets of width-specific structures and function
prototypes. */
#define PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS \
PCRE2_TYPES_LIST \
PCRE2_STRUCTURE_LIST \
PCRE2_GENERAL_INFO_FUNCTIONS \
PCRE2_GENERAL_CONTEXT_FUNCTIONS \
PCRE2_COMPILE_CONTEXT_FUNCTIONS \
PCRE2_MATCH_CONTEXT_FUNCTIONS \
PCRE2_COMPILE_FUNCTIONS \
PCRE2_PATTERN_INFO_FUNCTIONS \
PCRE2_MATCH_FUNCTIONS \
PCRE2_SUBSTRING_FUNCTIONS \
PCRE2_SERIALIZE_FUNCTIONS \
PCRE2_SUBSTITUTE_FUNCTION \
PCRE2_JIT_FUNCTIONS \
PCRE2_OTHER_FUNCTIONS
#define PCRE2_LOCAL_WIDTH 8
PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS
#undef PCRE2_LOCAL_WIDTH
#define PCRE2_LOCAL_WIDTH 16
PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS
#undef PCRE2_LOCAL_WIDTH
#define PCRE2_LOCAL_WIDTH 32
PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS
#undef PCRE2_LOCAL_WIDTH
/* Undefine the list macros; they are no longer needed. */
#undef PCRE2_TYPES_LIST
#undef PCRE2_STRUCTURE_LIST
#undef PCRE2_GENERAL_INFO_FUNCTIONS
#undef PCRE2_GENERAL_CONTEXT_FUNCTIONS
#undef PCRE2_COMPILE_CONTEXT_FUNCTIONS
#undef PCRE2_MATCH_CONTEXT_FUNCTIONS
#undef PCRE2_COMPILE_FUNCTIONS
#undef PCRE2_PATTERN_INFO_FUNCTIONS
#undef PCRE2_MATCH_FUNCTIONS
#undef PCRE2_SUBSTRING_FUNCTIONS
#undef PCRE2_SERIALIZE_FUNCTIONS
#undef PCRE2_SUBSTITUTE_FUNCTION
#undef PCRE2_JIT_FUNCTIONS
#undef PCRE2_OTHER_FUNCTIONS
#undef PCRE2_TYPES_STRUCTURES_AND_FUNCTIONS
/* PCRE2_CODE_UNIT_WIDTH must be defined. If it is 8, 16, or 32, redefine
PCRE2_SUFFIX to use it. If it is 0, undefine the other macros and make
PCRE2_SUFFIX a no-op. Otherwise, generate an error. */
#undef PCRE2_SUFFIX
#ifndef PCRE2_CODE_UNIT_WIDTH
#error PCRE2_CODE_UNIT_WIDTH must be defined before including pcre2.h.
#error Use 8, 16, or 32; or 0 for a multi-width application.
#else /* PCRE2_CODE_UNIT_WIDTH is defined */
#if PCRE2_CODE_UNIT_WIDTH == 8 || \
PCRE2_CODE_UNIT_WIDTH == 16 || \
PCRE2_CODE_UNIT_WIDTH == 32
#define PCRE2_SUFFIX(a) PCRE2_GLUE(a, PCRE2_CODE_UNIT_WIDTH)
#elif PCRE2_CODE_UNIT_WIDTH == 0
#undef PCRE2_JOIN
#undef PCRE2_GLUE
#define PCRE2_SUFFIX(a) a
#else
#error PCRE2_CODE_UNIT_WIDTH must be 0, 8, 16, or 32.
#endif
#endif /* PCRE2_CODE_UNIT_WIDTH is defined */
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* End of pcre2.h */

282
src/i3_command.cpp Normal file
View File

@ -0,0 +1,282 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "i3_command.h"
#include "jpcre2.h"
#include "window_helpers.h"
#include <cstring>
#include <ranges>
#define MIR_LOG_COMPONENT "miracle::i3_command"
#include <mir/log.h>
using namespace miracle;
namespace
{
const char* CLASS_STRING = "class";
const char* INSTANCE_STRING = "instance";
const char* WINDOW_ROLE_STRING = "window_role";
const char* MACHINE_STRING = "machine";
const char* ID_STRING = "id";
const char* TITLE_STRING = "title";
const char* URGENT_STRING = "urgent";
const char* WORKSPACE_STRING = "workspace";
const char* ALL_STRING = "all";
const char* FLOATING_STRING = "floating";
const char* TILING_STRING = "tiling";
inline bool try_parse_i3_scope(
std::string_view const& view,
int& ptr,
const char* v,
bool has_value)
{
auto const v_len = strlen(v);
bool starts_with = view.compare(ptr, v_len, v) == 0;
if (!starts_with)
return false;
int possible_new_ptr = ptr + v_len;
if (has_value)
{
if (view[possible_new_ptr] != '=')
return false;
ptr = possible_new_ptr;
return true;
}
else
{
if (view[possible_new_ptr] == ']' || view[possible_new_ptr == ' '])
{
ptr = possible_new_ptr;
return true;
}
return false;
}
}
}
// https://i3wm.org/docs/userguide.html#command_criteria
std::vector<I3Scope> I3Scope::parse(std::string_view const& view, int& ptr)
{
if (view[0] != '[')
{
ptr = 0;
return {};
}
std::vector<I3Scope> result;
ptr = 1; // Start past the opening bracket
while (view[ptr] != ']') // End when we encounter the closing bracket
{
I3Scope next;
if (try_parse_i3_scope(view, ptr, CLASS_STRING, true))
next.type = I3ScopeType::class_;
else if (try_parse_i3_scope(view, ptr, WINDOW_ROLE_STRING, true))
next.type = I3ScopeType::window_role;
else if (try_parse_i3_scope(view, ptr, MACHINE_STRING, true))
next.type = I3ScopeType::machine;
else if (try_parse_i3_scope(view, ptr, ID_STRING, true))
next.type = I3ScopeType::id;
else if (try_parse_i3_scope(view, ptr, INSTANCE_STRING, true))
next.type = I3ScopeType::instance;
else if (try_parse_i3_scope(view, ptr, TITLE_STRING, true))
next.type = I3ScopeType::title;
else if (try_parse_i3_scope(view, ptr, URGENT_STRING, true))
next.type = I3ScopeType::urgent;
else if (try_parse_i3_scope(view, ptr, WORKSPACE_STRING, true))
next.type = I3ScopeType::workspace;
else if (try_parse_i3_scope(view, ptr, ALL_STRING, false))
{
next.type = I3ScopeType::all;
result.push_back(next);
continue;
}
else if (try_parse_i3_scope(view, ptr, FLOATING_STRING, false))
{
next.type = I3ScopeType::floating;
result.push_back(next);
continue;
}
else if (try_parse_i3_scope(view, ptr, TILING_STRING, false))
{
next.type = I3ScopeType::tiling;
result.push_back(next);
continue;
}
else
{
ptr++;
continue;
}
// If we get here, it is assumed that we need to also parse a regex
ptr++;
if (view[ptr] != '"')
continue;
ptr++;
auto start = ptr;
for (; ptr < view.size(); ptr++)
{
if (view[ptr] == '"')
break;
}
if (ptr == view.size())
{
// TODO: ERROR Haven't encountered a closing quote
break;
}
ptr++;
next.regex = view.substr(start, ptr - start - 1);
// TODO: Verify if we have a valid value here or not
result.push_back(next);
}
return result;
}
bool I3ScopedCommandList::meets_criteria(miral::Window const& window, miral::WindowManagerTools& window_manager_tools) const
{
typedef jpcre2::select<char> jp;
auto const& window_info = window_manager_tools.info_for(window);
auto metadata = window_helpers::get_metadata(window, window_manager_tools);
if (!metadata)
return false;
for (auto const& criteria : scope)
{
switch (criteria.type)
{
case I3ScopeType::all:
break;
case I3ScopeType::title:
{
jp::Regex re(criteria.regex.value());
auto const& name = window_info.name();
if (!re.match(name))
return false;
}
default:
break;
}
}
return true;
}
namespace
{
bool equals(std::string_view const& s, const char* v)
{
// TODO: Perhaps this is a bit naive, as it is basically a "startswith"
return strncmp(s.data(), v, strlen(v)) == 0;
}
}
std::vector<I3ScopedCommandList> I3ScopedCommandList::parse(std::string_view const& view)
{
std::vector<I3ScopedCommandList> list;
// First, split the view by semicolons, as that will denote different possible scopes
for (auto const& scope : ((view) | std::ranges::views::split(';')))
{
I3ScopedCommandList result;
// Next, parse the scope
int ptr = 0;
auto sub_view = std::string_view(scope.data());
result.scope = I3Scope::parse(sub_view, ptr);
// Next, split the sub_view by commas to get the list of commands with the scope
for (auto const& command_view : (std::string_view(&sub_view[ptr]) | std::ranges::views::split(',')))
{
I3Command next_command = { I3CommandType::none };
// Next, we can now read the command tokens space-by-space
for (auto const& command_token : ((command_view) | std::ranges::views::split(' ')))
{
if (next_command.type == I3CommandType::none)
{
if (equals(command_token.data(), "exec"))
next_command.type = I3CommandType::exec;
else if (equals(command_token.data(), "split"))
next_command.type = I3CommandType::split;
else if (equals(command_token.data(), "layout"))
next_command.type = I3CommandType::layout;
else if (equals(command_token.data(), "focus"))
next_command.type = I3CommandType::focus;
else if (equals(command_token.data(), "move"))
next_command.type = I3CommandType::move;
else if (equals(command_token.data(), "swap"))
next_command.type = I3CommandType::swap;
else if (equals(command_token.data(), "sticky"))
next_command.type = I3CommandType::sticky;
else if (equals(command_token.data(), "workspace"))
next_command.type = I3CommandType::workspace;
else if (equals(command_token.data(), "mark"))
next_command.type = I3CommandType::mark;
else if (equals(command_token.data(), "title_format"))
next_command.type = I3CommandType::title_format;
else if (equals(command_token.data(), "title_window_icon"))
next_command.type = I3CommandType::title_window_icon;
else if (equals(command_token.data(), "border"))
next_command.type = I3CommandType::border;
else if (equals(command_token.data(), "shm_log"))
next_command.type = I3CommandType::shm_log;
else if (equals(command_token.data(), "debug_log"))
next_command.type = I3CommandType::debug_log;
else if (equals(command_token.data(), "restart"))
next_command.type = I3CommandType::restart;
else if (equals(command_token.data(), "reload"))
next_command.type = I3CommandType::reload;
else if (equals(command_token.data(), "exit"))
next_command.type = I3CommandType::exit;
else if (equals(command_token.data(), "scratchpad"))
next_command.type = I3CommandType::scratchpad;
else if (equals(command_token.data(), "nop"))
next_command.type = I3CommandType::nop;
else if (equals(command_token.data(), "i3_bar"))
next_command.type = I3CommandType::i3_bar;
else if (equals(command_token.data(), "gaps"))
next_command.type = I3CommandType::gaps;
else
{
mir::log_error("Invalid i3 command type: %s", command_token.data());
continue;
}
}
else
{
next_command.arguments.emplace_back(command_token.data());
}
}
result.commands.push_back(next_command);
}
list.push_back(result);
}
return list;
}

108
src/i3_command.h Normal file
View File

@ -0,0 +1,108 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#ifndef MIRACLEWM_I3_COMMAND_H
#define MIRACLEWM_I3_COMMAND_H
#include <miral/window.h>
#include <miral/window_manager_tools.h>
#include <optional>
#include <string>
#include <vector>
namespace miracle
{
enum class I3CommandType
{
none,
exec,
split,
layout,
focus,
move,
swap,
sticky,
workspace,
mark,
title_format,
title_window_icon,
border,
shm_log,
debug_log,
restart,
reload,
exit,
scratchpad,
nop,
i3_bar,
gaps
};
enum class I3ScopeType
{
none,
all,
machine,
title,
urgent,
workspace,
con_mark,
con_id,
floating,
floating_from,
tiling,
tiling_from,
/// TODO: X11-only
class_,
/// TODO: X11-only
instance,
/// TODO: X11-only
window_role,
/// TODO: X11-only
window_type,
// TODO: X11-only
id,
};
struct I3Scope
{
I3ScopeType type = I3ScopeType::none;
std::optional<std::string> regex;
/// Assumes that the provided string_view is in [] brackets
static std::vector<I3Scope> parse(std::string_view const&, int& ptr);
};
struct I3Command
{
I3CommandType type = I3CommandType::none;
std::vector<std::string> arguments;
};
struct I3ScopedCommandList
{
std::vector<I3Command> commands;
std::vector<I3Scope> scope;
bool meets_criteria(miral::Window const&, miral::WindowManagerTools&) const;
static std::vector<I3ScopedCommandList> parse(std::string_view const&);
};
}
#endif // MIRACLEWM_I3_COMMAND_H

183
src/i3_command_executor.cpp Normal file
View File

@ -0,0 +1,183 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "i3_command_executor.h"
#include "leaf_node.h"
#include "parent_node.h"
#include "policy.h"
#include "window_helpers.h"
#define MIR_LOG_COMPONENT "miracle"
#include <mir/log.h>
#include <miral/application_info.h>
using namespace miracle;
I3CommandExecutor::I3CommandExecutor(
miracle::Policy& policy,
WorkspaceManager& workspace_manager,
miral::WindowManagerTools const& tools) :
policy { policy },
workspace_manager { workspace_manager },
tools { tools }
{
}
void I3CommandExecutor::process(miracle::I3ScopedCommandList const& command_list)
{
for (auto const& command : command_list.commands)
{
switch (command.type)
{
case I3CommandType::focus:
process_focus(command, command_list);
break;
default:
break;
}
}
}
miral::Window I3CommandExecutor::get_window_meeting_criteria(I3ScopedCommandList const& command_list)
{
miral::Window result;
tools.find_application([&](miral::ApplicationInfo const& info)
{
for (auto const& window : info.windows())
{
if (command_list.meets_criteria(window, tools))
{
result = window;
return true;
}
}
return false;
});
return result;
}
void I3CommandExecutor::process_focus(I3Command const& command, I3ScopedCommandList const& command_list)
{
auto active_output = policy.get_active_output();
if (!active_output)
{
mir::log_warning("Trying to process I3 focus command, but output is not set");
return;
}
// https://i3wm.org/docs/userguide.html#_focusing_moving_containers
if (command.arguments.empty())
{
if (command_list.scope.empty())
{
mir::log_warning("Focus command expected scope but none was provided");
return;
}
auto window = get_window_meeting_criteria(command_list);
if (window)
active_output->select_window(window);
return;
}
auto const& arg = command.arguments.front();
if (arg == "workspace")
{
if (command_list.scope.empty())
{
mir::log_warning("Focus 'workspace' command expected scope but none was provided");
return;
}
auto window = get_window_meeting_criteria(command_list);
auto metadata = window_helpers::get_metadata(window, tools);
if (metadata)
workspace_manager.request_focus(metadata->get_workspace());
}
else if (arg == "left")
active_output->select(Direction::left);
else if (arg == "right")
active_output->select(Direction::right);
else if (arg == "up")
active_output->select(Direction::up);
else if (arg == "down")
active_output->select(Direction::down);
else if (arg == "parent")
mir::log_warning("'focus parent' is not supported, see https://github.com/mattkae/miracle-wm/issues/117"); // TODO
else if (arg == "child")
mir::log_warning("'focus child' is not supported, see https://github.com/mattkae/miracle-wm/issues/117"); // TODO
else if (arg == "prev")
{
auto active_window = tools.active_window();
if (!active_window)
return;
auto metadata = window_helpers::get_metadata(active_window, tools);
if (!metadata)
return;
if (metadata->get_type() != WindowType::tiled)
{
mir::log_warning("Cannot focus prev when a tiling window is not selected");
return;
}
auto node = metadata->get_tiling_node();
auto parent = node->get_parent().lock();
auto index = parent->get_index_of_node(node);
if (index != 0)
{
auto node_to_select = parent->get_nth_window(index - 1);
active_output->select_window(node_to_select->get_window());
}
}
else if (arg == "next")
{
auto active_window = tools.active_window();
if (!active_window)
return;
auto metadata = window_helpers::get_metadata(active_window, tools);
if (!metadata)
return;
if (metadata->get_type() != WindowType::tiled)
{
mir::log_warning("Cannot focus prev when a tiling window is not selected");
return;
}
auto node = metadata->get_tiling_node();
auto parent = node->get_parent().lock();
auto index = parent->get_index_of_node(node);
if (index != parent->num_nodes() - 1)
{
auto node_to_select = parent->get_nth_window(index + 1);
active_output->select_window(node_to_select->get_window());
}
}
else if (arg == "floating")
mir::log_warning("'focus floating' is not supported, see https://github.com/mattkae/miracle-wm/issues/117"); // TODO
else if (arg == "tiling")
mir::log_warning("'focus tiling' is not supported, see https://github.com/mattkae/miracle-wm/issues/117"); // TODO
else if (arg == "mode_toggle")
mir::log_warning("'focus mode_toggle' is not supported, see https://github.com/mattkae/miracle-wm/issues/117"); // TODO
else if (arg == "output")
mir::log_warning("'focus output' is not supported, see https://github.com/canonical/mir/issues/3357"); // TODO
}

49
src/i3_command_executor.h Normal file
View File

@ -0,0 +1,49 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#ifndef MIRACLEWM_I_3_COMMAND_EXECUTOR_H
#define MIRACLEWM_I_3_COMMAND_EXECUTOR_H
#include "i3_command.h"
#include <mir/glib_main_loop.h>
namespace miracle
{
class Policy;
class WorkspaceManager;
/// Processes all commands coming from i3 IPC. This class is mostly for organizational
/// purposes, as a lot of logic is associated with processing these operations.
class I3CommandExecutor
{
public:
I3CommandExecutor(Policy&, WorkspaceManager&, miral::WindowManagerTools const&);
void process(I3ScopedCommandList const&);
private:
Policy& policy;
WorkspaceManager& workspace_manager;
miral::WindowManagerTools tools;
miral::Window get_window_meeting_criteria(I3ScopedCommandList const&);
void process_focus(I3Command const&, I3ScopedCommandList const&);
};
} // miracle
#endif // MIRACLEWM_I_3_COMMAND_EXECUTOR_H

View File

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MIR_LOG_COMPONENT "miracle_ipc"
#include "ipc.h"
#include "i3_command_executor.h"
#include "output_content.h"
#include "policy.h"
@ -39,6 +40,7 @@ static const char ipc_magic[] = { 'i', '3', '-', 'i', 'p', 'c' };
namespace
{
struct sockaddr_un* ipc_user_sockaddr()
{
auto ipc_sockaddr = (sockaddr_un*)malloc(sizeof(struct sockaddr_un));
@ -128,9 +130,15 @@ json outputs_to_json(std::vector<std::shared_ptr<OutputContent>> const& outputs)
}
Ipc::Ipc(miral::MirRunner& runner, miracle::WorkspaceManager& workspace_manager, Policy& policy) :
Ipc::Ipc(miral::MirRunner& runner,
miracle::WorkspaceManager& workspace_manager,
Policy& policy,
std::shared_ptr<mir::ServerActionQueue> const& queue,
I3CommandExecutor& executor) :
workspace_manager { workspace_manager },
policy { policy }
policy { policy },
queue { queue },
executor { executor }
{
auto ipc_socket_raw = socket(AF_UNIX, SOCK_STREAM, 0);
if (ipc_socket_raw == -1)
@ -387,6 +395,21 @@ void Ipc::handle_command(miracle::Ipc::IpcClient& client, uint32_t payload_lengt
switch (payload_type)
{
case IPC_COMMAND:
{
auto result = parse_i3_command(std::string_view(buf));
if (result)
{
const std::string msg = "[{\"success\": true}]";
send_reply(client, payload_type, msg);
}
else
{
const std::string msg = "[{\"success\": false, \"parse_error\": true}]";
send_reply(client, payload_type, msg);
}
break;
}
case IPC_GET_WORKSPACES:
{
json j = json::array();
@ -506,4 +529,36 @@ void Ipc::handle_writeable(miracle::Ipc::IpcClient& client)
}
client.write_buffer_len = 0;
}
namespace
{
bool equals(std::string_view const& s, const char* v)
{
// TODO: Perhaps this is a bit naive, as it is basically a "startswith"
return strncmp(s.data(), v, strlen(v)) == 0;
}
}
bool Ipc::parse_i3_command(std::string_view const& command)
{
{
std::unique_lock lock(pending_commands_mutex);
pending_commands = I3ScopedCommandList::parse(command);
}
queue->enqueue(this, [&]()
{
size_t num_processed = 0;
{
std::shared_lock lock(pending_commands_mutex);
for (auto const& c : pending_commands)
executor.process(c);
num_processed = pending_commands.size();
}
std::unique_lock lock(pending_commands_mutex);
pending_commands.erase(pending_commands.begin(), pending_commands.begin() + num_processed);
});
return true;
}

View File

@ -18,10 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef MIRACLEWM_IPC_H
#define MIRACLEWM_IPC_H
#include "i3_command.h"
#include "i3_command_executor.h"
#include "workspace_manager.h"
#include "workspace_observer.h"
#include <mir/fd.h>
#include <mir/server_action_queue.h>
#include <miral/runner.h>
#include <shared_mutex>
#include <vector>
struct sockaddr_un;
@ -75,7 +79,11 @@ enum IpcCommandType
class Ipc : public WorkspaceObserver
{
public:
Ipc(miral::MirRunner& runner, WorkspaceManager&, Policy& policy);
Ipc(miral::MirRunner& runner,
WorkspaceManager&,
Policy& policy,
std::shared_ptr<mir::ServerActionQueue> const&,
I3CommandExecutor&);
void on_created(std::shared_ptr<OutputContent> const& info, int key) override;
void on_removed(std::shared_ptr<OutputContent> const& info, int key) override;
@ -99,12 +107,17 @@ private:
std::unique_ptr<miral::FdHandle> socket_handle;
sockaddr_un* ipc_sockaddr = nullptr;
std::vector<IpcClient> clients;
std::vector<I3ScopedCommandList> pending_commands;
mutable std::shared_mutex pending_commands_mutex;
std::shared_ptr<mir::ServerActionQueue> queue;
I3CommandExecutor& executor;
void disconnect(IpcClient& client);
IpcClient& get_client(int fd);
void handle_command(IpcClient& client, uint32_t payload_length, IpcCommandType payload_type);
void send_reply(IpcClient& client, IpcCommandType command_type, std::string const& payload);
void handle_writeable(IpcClient& client);
bool parse_i3_command(std::string_view const& command);
};
}

View File

@ -35,6 +35,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using namespace miral;
/// Wraps another miral API so that we can gain access to the underlying Server.
class ServerMiddleman
{
public:
explicit ServerMiddleman(std::function<void(::mir::Server&)> const& f) :
f { f }
{
}
void operator()(mir::Server& server) const
{
f(server);
}
private:
std::function<void(::mir::Server&)> f;
};
int main(int argc, char const* argv[])
{
MirRunner runner { argc, argv };
@ -51,10 +68,16 @@ int main(int argc, char const* argv[])
setenv(env.key.c_str(), env.value.c_str(), 1);
}
WindowManagerOptions window_managers {
add_window_manager_policy<miracle::Policy>(
"tiling", external_client_launcher, runner, config)
};
WindowManagerOptions* options;
auto window_managers = ServerMiddleman(
[&](auto& server)
{
options = new WindowManagerOptions {
add_window_manager_policy<miracle::Policy>(
"tiling", external_client_launcher, runner, config, server)
};
(*options)(server);
});
Keymap config_keymap;

View File

@ -87,7 +87,7 @@ std::shared_ptr<WindowMetadata> OutputContent::advise_new_window(miral::WindowIn
case WindowType::tiled:
{
auto node = get_active_tree()->advise_new_window(window_info);
metadata = std::make_shared<WindowMetadata>(WindowType::tiled, window_info.window(), this);
metadata = std::make_shared<WindowMetadata>(WindowType::tiled, window_info.window(), this, active_workspace);
metadata->associate_to_node(node);
break;
}
@ -101,7 +101,7 @@ std::shared_ptr<WindowMetadata> OutputContent::advise_new_window(miral::WindowIn
{
tools.select_active_window(window_info.window());
}
metadata = std::make_shared<WindowMetadata>(WindowType::other, window_info.window(), nullptr);
metadata = std::make_shared<WindowMetadata>(WindowType::other, window_info.window());
break;
default:
mir::log_error("Unsupported window type: %d", (int)type);
@ -377,6 +377,11 @@ void OutputContent::select_window_from_point(int x, int y)
}
}
void OutputContent::select_window(miral::Window const& window)
{
tools.select_active_window(window);
}
void OutputContent::advise_new_workspace(int workspace)
{
workspaces.push_back(
@ -583,7 +588,7 @@ void OutputContent::request_toggle_active_float()
WindowSpecification spec = floating_window_manager.place_new_window(
tools.info_for(active_window.application()),
prev_spec);
spec.userdata() = std::make_shared<WindowMetadata>(WindowType::floating, active_window, this);
spec.userdata() = std::make_shared<WindowMetadata>(WindowType::floating, active_window, this, active_workspace);
spec.top_left() = geom::Point { active_window.top_left().x.as_int() + 20, active_window.top_left().y.as_int() + 20 };
tools.modify_window(active_window, spec);
@ -607,6 +612,29 @@ void OutputContent::request_toggle_active_float()
}
}
miral::Window OutputContent::find_window_on_active_workspace_matching_predicate(
std::function<bool(miral::Window const&)> const& f) const
{
auto workspace = get_active_workspace();
workspace->get_tree()->find_node([&](std::shared_ptr<Node> const& node)
{
if (auto leaf_node = Node::as_leaf(node))
{
if (f(leaf_node->get_window()))
return true;
}
return false;
});
for (auto const& floating : workspace->get_floating_windows())
{
if (f(floating))
return floating;
}
return {};
}
void OutputContent::add_immediately(miral::Window& window)
{
auto& prev_info = tools.info_for(window);

View File

@ -68,6 +68,7 @@ public:
MirWindowState new_state,
const mir::geometry::Rectangle& new_placement);
void select_window_from_point(int x, int y);
void select_window(miral::Window const&);
void advise_new_workspace(int workspace);
void advise_workspace_deleted(int workspace);
bool advise_workspace_active(int workspace);
@ -88,6 +89,7 @@ public:
void update_area(geom::Rectangle const& area);
std::vector<miral::Window> collect_all_windows() const;
void request_toggle_active_float();
miral::Window find_window_on_active_workspace_matching_predicate(std::function<bool(miral::Window const&)> const&) const;
/// Immediately requests that the provided window be added to the output
/// with the provided type. This is a deviation away from the typical

View File

@ -15,7 +15,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "miral/window_specification.h"
#include "window_metadata.h"
#define MIR_LOG_COMPONENT "miracle"
@ -28,10 +27,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <limits>
#include <mir/geometry/rectangle.h>
#include <mir/log.h>
#include <mir/server.h>
#include <mir_toolkit/events/enums.h>
#include <miral/application_info.h>
#include <miral/runner.h>
#include <miral/toolkit_event.h>
#include <miral/window_specification.h>
#include <miral/zone.h>
using namespace miracle;
@ -45,7 +46,8 @@ Policy::Policy(
miral::WindowManagerTools const& tools,
miral::ExternalClientLauncher const& external_client_launcher,
miral::MirRunner& runner,
std::shared_ptr<MiracleConfig> const& config) :
std::shared_ptr<MiracleConfig> const& config,
mir::Server const& server) :
window_manager_tools { tools },
floating_window_manager(tools, config->get_input_event_modifier()),
external_client_launcher { external_client_launcher },
@ -56,7 +58,8 @@ Policy::Policy(
workspace_observer_registrar,
[&]()
{ return get_active_output(); }) },
ipc { std::make_shared<Ipc>(runner, workspace_manager, *this) },
i3_command_executor(*this, workspace_manager, tools),
ipc { std::make_shared<Ipc>(runner, workspace_manager, *this, server.the_main_loop(), i3_command_executor) },
node_interface(tools)
{
workspace_observer_registrar.register_interest(ipc);
@ -309,7 +312,7 @@ void Policy::advise_new_window(miral::WindowInfo const& window_info)
// windows are considered to be in the "other" category until
// we have more data on them.
orphaned_window_list.push_back(window);
auto metadata = std::make_shared<WindowMetadata>(WindowType::other, window_info.window(), nullptr);
auto metadata = std::make_shared<WindowMetadata>(WindowType::other, window_info.window());
miral::WindowSpecification spec;
spec.userdata() = metadata;
window_manager_tools.modify_window(window, spec);

View File

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef MIRACLE_POLICY_H
#define MIRACLE_POLICY_H
#include "i3_command_executor.h"
#include "ipc.h"
#include "miracle_config.h"
#include "output_content.h"
@ -49,7 +50,8 @@ public:
miral::WindowManagerTools const&,
miral::ExternalClientLauncher const&,
miral::MirRunner&,
std::shared_ptr<MiracleConfig> const&);
std::shared_ptr<MiracleConfig> const&,
mir::Server const&);
~Policy() override;
bool handle_keyboard_event(MirKeyboardEvent const* event) override;
@ -110,6 +112,7 @@ private:
WorkspaceManager workspace_manager;
std::shared_ptr<Ipc> ipc;
WindowManagerToolsTilingInterface node_interface;
I3CommandExecutor i3_command_executor;
};
}

View File

@ -784,20 +784,37 @@ bool TilingWindowTree::constrain(miral::Window& window)
namespace
{
void foreach_node_internal(std::function<void(std::shared_ptr<Node>)> const& f, std::shared_ptr<Node> const& parent)
std::shared_ptr<Node> foreach_node_internal(
std::function<bool(std::shared_ptr<Node>)> const& f,
std::shared_ptr<Node> const& parent)
{
f(parent);
if (f(parent))
return parent;
if (parent->is_leaf())
return;
return nullptr;
for (auto& node : Node::as_lane(parent)->get_sub_nodes())
foreach_node_internal(f, node);
{
if (auto result = foreach_node_internal(f, node))
return result;
}
return nullptr;
}
}
void TilingWindowTree::foreach_node(std::function<void(std::shared_ptr<Node>)> const& f)
{
foreach_node_internal(f, root_lane);
foreach_node_internal(
[&](auto const& node)
{ f(node); return false; },
root_lane);
}
std::shared_ptr<Node> TilingWindowTree::find_node(std::function<bool(std::shared_ptr<Node> const&)> const& f)
{
return foreach_node_internal(f, root_lane);
}
void TilingWindowTree::hide()

View File

@ -48,7 +48,7 @@ public:
~TilingWindowTree();
/// Makes space for the new window and returns its specified spot in the grid. Note that the returned
/// position is the position WITH GAPS.
/// position is the position WITH gaps.
miral::WindowSpecification allocate_position(const miral::WindowSpecification& requested_specification);
std::shared_ptr<LeafNode> advise_new_window(miral::WindowInfo const&);
@ -105,6 +105,8 @@ public:
void foreach_node(std::function<void(std::shared_ptr<Node>)> const&);
std::shared_ptr<Node> find_node(std::function<bool(std::shared_ptr<Node> const&)> const&);
/// Hides the entire tree
void hide();

View File

@ -19,13 +19,20 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using namespace miracle;
WindowMetadata::WindowMetadata(WindowType type, miral::Window const& window) :
WindowMetadata(type, window, nullptr, -1)
{
}
WindowMetadata::WindowMetadata(
miracle::WindowType type,
miral::Window const& window,
OutputContent* output) :
OutputContent* output,
int workspace) :
type { type },
window { window },
output { output }
output { output },
workspace { workspace }
{
}
@ -50,4 +57,21 @@ void WindowMetadata::toggle_pin_to_desktop()
{
is_pinned = !is_pinned;
}
}
void WindowMetadata::set_workspace(int in_workspace)
{
workspace = in_workspace;
}
int WindowMetadata::get_workspace() const
{
return workspace;
}
std::shared_ptr<LeafNode> WindowMetadata::get_tiling_node() const
{
if (type == WindowType::tiled)
return tiling_node;
return nullptr;
}

View File

@ -40,26 +40,25 @@ enum class WindowType
class WindowMetadata
{
public:
WindowMetadata(WindowType type, miral::Window const& window, OutputContent* output);
WindowMetadata(WindowType type, miral::Window const& window);
WindowMetadata(WindowType type, miral::Window const& window, OutputContent* output, int workspace);
void associate_to_node(std::shared_ptr<LeafNode> const&);
miral::Window& get_window() { return window; }
std::shared_ptr<LeafNode> get_tiling_node() const
{
if (type == WindowType::tiled)
return tiling_node;
return nullptr;
}
std::shared_ptr<LeafNode> get_tiling_node() const;
WindowType get_type() const { return type; }
OutputContent* get_output() const { return output; }
bool get_is_pinned() const { return is_pinned; }
void set_restore_state(MirWindowState state);
MirWindowState consume_restore_state();
void toggle_pin_to_desktop();
void set_workspace(int workspace);
int get_workspace() const;
private:
WindowType type;
miral::Window window;
OutputContent* output;
int workspace;
std::shared_ptr<LeafNode> tiling_node;
MirWindowState restore_state;
bool is_pinned = false;

View File

@ -112,6 +112,7 @@ bool WorkspaceManager::move_active_to_workspace(std::shared_ptr<OutputContent> s
tools_.modify_window(window, spec);
auto new_node = screen_to_move_to->get_active_tree()->advise_new_window(prev_info);
metadata->set_workspace(workspace);
metadata->associate_to_node(new_node);
miral::WindowSpecification next_spec;
next_spec.userdata() = metadata;

View File

@ -16,7 +16,8 @@ pkg_check_modules(YAML REQUIRED IMPORTED_TARGET yaml-cpp)
add_executable(miracle-wm-tests
miracle_config_test.cpp
tree_test.cpp)
tree_test.cpp
test_i3_command.cpp)
target_include_directories(miracle-wm-tests PUBLIC SYSTEM ${GTEST_INCLUDE_DIRS} ${MIRAL_INCLUDE_DIRS})
target_link_libraries(miracle-wm-tests GTest::gtest_main miracle-wm-implementation ${GTEST_LIBRARIES} ${MIRAL_LDFLAGS} PkgConfig::YAML pthread)

87
tests/test_i3_command.cpp Normal file
View File

@ -0,0 +1,87 @@
/**
Copyright (C) 2024 Matthew Kosarek
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
**/
#include "i3_command.h"
#include <gtest/gtest.h>
using namespace miracle;
class I3CommandTest : public testing::Test
{
};
TEST_F(I3CommandTest, TestClassParsing)
{
std::string v = "[class=\"XYZ\"]";
int ptr;
auto scope = I3Scope::parse(v, ptr);
ASSERT_EQ(scope[0].type, I3ScopeType::class_);
ASSERT_EQ(scope[0].regex.value(), "XYZ");
}
TEST_F(I3CommandTest, TestAllParsing)
{
std::string v = "[all]";
int ptr;
auto scope = I3Scope::parse(v, ptr);
ASSERT_EQ(scope[0].type, I3ScopeType::all);
}
TEST_F(I3CommandTest, TestMultipleParsing)
{
std::string v = "[class=\"Firefox\" window_role=\"About\"]";
int ptr;
auto scope = I3Scope::parse(v, ptr);
ASSERT_EQ(scope[0].type, I3ScopeType::class_);
ASSERT_EQ(scope[0].regex.value(), "Firefox");
ASSERT_EQ(scope[1].type, I3ScopeType::window_role);
ASSERT_EQ(scope[1].regex.value(), "About");
}
TEST_F(I3CommandTest, TestComplexClassParsing)
{
std::string v = "[class=\"^(?i)(?!firefox)(?!gnome-terminal).*\"]";
int ptr;
auto scope = I3Scope::parse(v, ptr);
ASSERT_EQ(scope[0].type, I3ScopeType::class_);
ASSERT_EQ(scope[0].regex.value(), "^(?i)(?!firefox)(?!gnome-terminal).*");
}
TEST_F(I3CommandTest, TestTilingParsing)
{
std::string v = "[tiling ]";
int ptr;
auto scope = I3Scope::parse(v, ptr);
ASSERT_EQ(scope[0].type, I3ScopeType::tiling);
}
TEST_F(I3CommandTest, TestFloatingParsing)
{
std::string v = "[floating ]";
int ptr;
auto scope = I3Scope::parse(v, ptr);
ASSERT_EQ(scope[0].type, I3ScopeType::floating);
}
TEST_F(I3CommandTest, CanParseSingleI3Command)
{
std::string v = "exec gedit";
auto commands = I3ScopedCommandList::parse(v);
ASSERT_EQ(commands[0].commands.size(), 1);
ASSERT_EQ(commands[0].commands[0].type, I3CommandType::exec);
ASSERT_EQ(commands[0].commands[0].arguments[0], "gedit");
}