Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new commands for window controls #47

Merged
merged 8 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ deps = [
dependency('wayland-server'),
dependency('xkbcommon'),
dependency('libconfig'),
dependency('json-c')
dependency('json-c'),
dependency('uuid')
]

executable(
Expand Down
145 changes: 137 additions & 8 deletions src/commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
#include "src/server.h"
#include "src/toplevel.h"
#include "src/workspace.h"
#include "wlr/util/log.h"
#include <stdio.h>
#include <string.h>
#include <wayland-util.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <json-c/json.h>

Expand All @@ -37,13 +39,19 @@ void window_command(char *tokens[], int ntokens, char *response,
void window_list_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
void window_switch_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
void window_cycle_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
void window_kill_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
void window_move_to_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
void workspace_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
void workspace_list_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
struct turtile_context *context);
void workspace_switch_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context);
struct turtile_context *conntext);
typedef struct {
char *cmd_name;
char *subcmd_name;
Expand All @@ -56,6 +64,9 @@ static command_t commands[] = {
{"exit", NULL, exit_command},
{"window", "list", window_list_command},
{"window", "switch", window_switch_command},
{"window", "cycle", window_cycle_command},
{"window", "kill", window_kill_command},
{"window", "move-to", window_move_to_command},
{"window", NULL, window_command},
{"workspace", "list", workspace_list_command},
{"workspace", "switch", workspace_switch_command},
Expand Down Expand Up @@ -148,9 +159,15 @@ void window_list_command(char *tokens[], int ntokens, char *response,
if (toplevel->xdg_toplevel) {
const char *title = toplevel->xdg_toplevel->title ?
toplevel->xdg_toplevel->title : "Unnamed";
const char *app = toplevel->xdg_toplevel->app_id ?
toplevel->xdg_toplevel->title : "null";

// Create a JSON object for each window and populate its fields
struct json_object *json_window = json_object_new_object();
json_object_object_add(json_window, "id",
json_object_new_string(toplevel->id));
json_object_object_add(json_window, "app",
json_object_new_string(app));
json_object_object_add(json_window, "title",
json_object_new_string(title));
json_object_object_add(json_window, "workspace",
Expand All @@ -166,21 +183,133 @@ void window_list_command(char *tokens[], int ntokens, char *response,

void window_switch_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context){
// Cycle to the next toplevel
// TODO: add option to switch window by name
// Switch focus to designated toplevel
struct turtile_server *server = context->server;

if (wl_list_length(&server->toplevels) < 2) {
response = strdup("{\"error\": \"Only one current window open\"}");
return;
if(ntokens >= 1){
char *new_toplevel_id = tokens[0];
struct turtile_toplevel *toplevel;

wl_list_for_each(toplevel, &server->focus_toplevels, flink) {
if(strcmp(toplevel->id, new_toplevel_id) == 0){
focus_toplevel(toplevel, toplevel->xdg_toplevel->base->surface);
snprintf(response, MAX_MSG_SIZE,
"{\"success\": \"switching focus to: %s\"}",
toplevel->xdg_toplevel->title);
return;
}
}
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"window %s not found\"}", new_toplevel_id);

} else{
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"missing argument: window id\"}");
}
}

void window_cycle_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context){
// Cycle to the next toplevel in the same workspace
struct turtile_server *server = context->server;

struct wl_list workspace_toplevels;
get_workspace_toplevels(server->active_workspace, &workspace_toplevels);

if (wl_list_empty(&workspace_toplevels)){
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"Workspace is empty\"}");
return;
} else if (wl_list_length(&workspace_toplevels) < 2) {
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"Only one current window open\"}");
return;
}

struct turtile_toplevel *next_toplevel =
wl_container_of(server->toplevels.prev, next_toplevel, link);
wl_container_of(workspace_toplevels.next, next_toplevel, auxlink);
focus_toplevel(next_toplevel, next_toplevel->xdg_toplevel->base->surface);
snprintf(response, MAX_MSG_SIZE, "{\"success\": \"switching focus to: %s\"}",
next_toplevel->xdg_toplevel->title);
}

void window_kill_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context){
// kill designated toplevel
struct turtile_server *server = context->server;
struct turtile_toplevel *toplevel;

if(ntokens >= 1){
char *new_toplevel_id = tokens[0];

wl_list_for_each(toplevel, &server->focus_toplevels, flink) {
if(strcmp(toplevel->id, new_toplevel_id) == 0){
kill_toplevel(toplevel);
snprintf(response, MAX_MSG_SIZE,
"{\"success\": \"kill: %s\"}",
toplevel->xdg_toplevel->title);
return;
}
}
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"window %s not found\"}", new_toplevel_id);

} else{
toplevel = get_first_toplevel(server);
kill_toplevel(toplevel);
snprintf(response, MAX_MSG_SIZE,
"{\"success\": \"kill: %s\"}", toplevel->xdg_toplevel->title);
}
}

void window_move_to_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context){
struct turtile_server *server = context->server;

if (ntokens >= 1) {
char *target_workspace_name = tokens[0];
struct turtile_workspace *target_workspace =
get_workspace(server, target_workspace_name);

if (!target_workspace) {
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"workspace not found\"}");
return;
}

struct turtile_toplevel *toplevel_to_move;
if (ntokens >= 2) {
char *toplevel_id = tokens[1];

toplevel_to_move = get_toplevel(server, toplevel_id);

if (!toplevel_to_move) {
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"window %s not found\"}", toplevel_id);
return;
}
} else {
toplevel_to_move = get_first_toplevel(server);
if (!toplevel_to_move) {
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"no focused window to move\"}");
return;
}
}

toplevel_to_move->workspace = target_workspace;
server_redraw_windows(server);

snprintf(response, MAX_MSG_SIZE,
"{\"success\": \"moved window %s to workspace %s\"}",
toplevel_to_move->xdg_toplevel->title, target_workspace->name);

} else {
snprintf(response, MAX_MSG_SIZE,
"{\"error\": \"missing argument: workspace name\"}");
}
}

void workspace_command(char *tokens[], int ntokens, char *response,
struct turtile_context *context){
// TODO: use this function as a help for the other workspace subcommands
Expand Down
39 changes: 39 additions & 0 deletions src/toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/types/wlr_xcursor_manager.h>
#include <uuid/uuid.h>

void focus_toplevel(struct turtile_toplevel *toplevel, struct wlr_surface *surface) {
/* Note: this function only deals with keyboard focus. */
Expand Down Expand Up @@ -75,6 +76,37 @@ void focus_toplevel(struct turtile_toplevel *toplevel, struct wlr_surface *surfa
server_redraw_windows(server);
}

void kill_toplevel(struct turtile_toplevel *toplevel) {
struct turtile_server *server = toplevel->server;

struct wl_list workspace_toplevels;
get_workspace_toplevels(server->active_workspace, &workspace_toplevels);

if (wl_list_empty(&workspace_toplevels)){
wlr_xdg_toplevel_send_close(toplevel->xdg_toplevel);
return;
} else if (wl_list_length(&workspace_toplevels) < 2) {
wlr_xdg_toplevel_send_close(toplevel->xdg_toplevel);
return;
}

struct turtile_toplevel *next_toplevel =
wl_container_of(workspace_toplevels.next, next_toplevel, auxlink);
focus_toplevel(next_toplevel, next_toplevel->xdg_toplevel->base->surface);

wlr_xdg_toplevel_send_close(toplevel->xdg_toplevel);
}

struct turtile_toplevel *get_toplevel(struct turtile_server *server, char *id) {
struct turtile_toplevel *toplevel;
wl_list_for_each(toplevel, &server->toplevels, link) {
if (strcmp(toplevel->id, id) == 0) {
return toplevel;
}
}
return NULL;
}

struct turtile_toplevel *get_first_toplevel(struct turtile_server *server) {
struct turtile_toplevel *toplevel;
wl_list_for_each(toplevel, &server->focus_toplevels, flink)
Expand Down Expand Up @@ -131,6 +163,13 @@ void xdg_toplevel_map(struct wl_listener *listener, void *data) {
/* Called when the surface is mapped, or ready to display on-screen. */
struct turtile_toplevel *toplevel = wl_container_of(listener, toplevel, map);

uuid_t uuid;
uuid_generate(uuid);
// Convert first 4 bytes of UUID to a short 8-character hexadecimal string
char short_uuid_str[9]; // 8 characters + null terminator
snprintf(short_uuid_str, sizeof(short_uuid_str), "%08x", *(uint32_t*)uuid);
strncpy(toplevel->id, short_uuid_str, sizeof(toplevel->id));

toplevel->workspace = toplevel->server->active_workspace;
wl_list_insert(&toplevel->server->toplevels, &toplevel->link);
wl_list_insert(&toplevel->server->focus_toplevels, &toplevel->flink);
Expand Down
24 changes: 22 additions & 2 deletions src/toplevel.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@
#include "src/output.h"
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <uuid/uuid.h>

struct turtile_toplevel {
struct wl_list link;
struct wl_list flink;
struct wl_list auxlink;

char id[9]; // 8 characters + null terminator
struct turtile_server *server;
struct wlr_xdg_toplevel *xdg_toplevel;
struct wlr_scene_tree *scene_tree;
struct turtile_workspace *workspace;
struct wlr_box geometry;

struct wl_listener map;
struct wl_listener unmap;
struct wl_listener commit;
Expand All @@ -43,8 +49,6 @@ struct turtile_toplevel {
struct wl_listener request_resize;
struct wl_listener request_maximize;
struct wl_listener request_fullscreen;

struct turtile_workspace *workspace;
};

/**
Expand All @@ -56,6 +60,22 @@ struct turtile_toplevel {
*/
void focus_toplevel(struct turtile_toplevel *toplevel,
struct wlr_surface *surface);
/**
* Send close request to toplevel, and focus next window
*
* @param toplevel The turtile toplevel to kill.
*/
void kill_toplevel(struct turtile_toplevel *toplevel);

/**
* Retrieves the toplevel with the given ID from the given server.
*
* @param server The turtile server to search for the toplevel on.
* @param id The ID of the toplevel to retrieve.
* @return A pointer to the toplevel with the given ID, or NULL
*/
struct turtile_toplevel *get_toplevel(struct turtile_server *server, char *id);

/**
* Retrieves the first toplevel on the active workspace of the given server.
* If no such toplevel is found, NULL is returned.
Expand Down
22 changes: 22 additions & 0 deletions src/workspace.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ struct turtile_workspace* create_workspace(struct turtile_server *server,
return new_workspace;
}

struct turtile_workspace *get_workspace(struct turtile_server *server,
char *name) {
struct turtile_workspace *workspace;
wl_list_for_each(workspace, &server->workspaces, link) {
if (strcmp(workspace->name, name) == 0) {
return workspace;
}
}
return NULL;
}

void switch_workspace(struct turtile_workspace *workspace){
if(workspace == NULL){
return;
Expand All @@ -65,3 +76,14 @@ struct turtile_workspace* create_workspaces_from_config(struct turtile_server *s
}
return active_workspace;
}

void get_workspace_toplevels(struct turtile_workspace *workspace,
struct wl_list *toplevels) {
struct turtile_server *server = workspace->server;
wl_list_init(toplevels);

struct turtile_toplevel *toplevel;
wl_list_for_each(toplevel, &server->focus_toplevels, flink)
if (toplevel->workspace == workspace)
wl_list_insert(toplevels, &toplevel->auxlink);
}
19 changes: 19 additions & 0 deletions src/workspace.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ struct turtile_workspace {
*/
struct turtile_workspace* create_workspace(struct turtile_server *server,
char *name);

/**
* Retrieves the workspace with the given name from the given server.
*
* @param server The turtile server to search for the workspace on.
* @param name The name of the workspace to retrieve.
* @return A pointer to the workspace with the given name, or NULL
*/
struct turtile_workspace *get_workspace(struct turtile_server *server,
char *name);
/**
* Switches the active workspace to the specified workspace.
*
Expand All @@ -61,4 +71,13 @@ void switch_workspace(struct turtile_workspace *workspace);
*/
struct turtile_workspace* create_workspaces_from_config(struct turtile_server *server);

/**
* Retrieves the list of toplevel windows in the specified workspace.
*
* @param workspace The workspace to retrieve toplevel windows from.
* @param toplevels The list to store the toplevel windows in, making use of
* auxlink
*/
void get_workspace_toplevels(struct turtile_workspace *workspace,
struct wl_list *toplevels);
#endif // WORKSPACE_H
Loading
Loading