From dc9092e312dc3664c74547e8cb86ca44acb920d6 Mon Sep 17 00:00:00 2001 From: Alec Murphy Date: Sun, 31 Jul 2022 15:39:08 -0400 Subject: [PATCH] Add files to repository --- KeyDev.HC | 48 ++++++++++++++ Load.HC | 11 ++++ Patch.HC | 88 +++++++++++++++++++++++++ README.md | 19 +++++- Run.HC | 3 + Saphir.HC | 19 ++++++ Status.HC | 7 ++ Win.HC | 175 +++++++++++++++++++++++++++++++++++++++++++++++++ screenshot.png | Bin 0 -> 13736 bytes 9 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 KeyDev.HC create mode 100644 Load.HC create mode 100644 Patch.HC create mode 100644 Run.HC create mode 100644 Saphir.HC create mode 100644 Status.HC create mode 100644 Win.HC create mode 100644 screenshot.png diff --git a/KeyDev.HC b/KeyDev.HC new file mode 100644 index 0000000..e8fded3 --- /dev/null +++ b/KeyDev.HC @@ -0,0 +1,48 @@ +U0 @saphir_key_nop() {} + +Bool @saphir_put_key(I64 ch, I64 sc) { + if (sc & SCF_ALT && !(sc & SCF_CTRL)) { + switch (ch) { + case 0: + switch (sc.u8[0]) { + case SC_CURSOR_UP: + @saphir_win_select(SAPHIR_WIN_UP); + return TRUE; + case SC_CURSOR_DOWN: + @saphir_win_select(SAPHIR_WIN_DOWN); + return TRUE; + case SC_CURSOR_LEFT: + @saphir_win_select(SAPHIR_WIN_LEFT); + return TRUE; + case SC_CURSOR_RIGHT: + @saphir_win_select(SAPHIR_WIN_RIGHT); + return TRUE; + } + break; + case 'h': + @saphir_split_horz; + return TRUE; + break; + case 'v': + @saphir_split_vert; + return TRUE; + break; + } + } + return FALSE; +} + +U64 @tos_fp_cbs_enabled = + keydev.fp_ctrl_alt_cbs; // Save pointer to TempleOS system-wide (CTRL-ALT) + // callbacks + +U64 @tos_fp_cbs_disabled = CAlloc(0xD0); +keydev.fp_ctrl_alt_cbs = + @tos_fp_cbs_disabled; // Disable TempleOS system-wide (CTRL-ALT) callbacks + +// FIXME: Ideally, we would add a new KeyDev here, but since we need to override +// the HomeKeyPlugIns, we will need to patch MyKeyDev instead. + +// KeyDevAdd(&@saphir_put_key, &MyPutS, 0x10000000, +// TRUE); // Enable Saphir keyboard shortcuts +@function_patch(&MyPutKey, &@saphir_put_key); diff --git a/Load.HC b/Load.HC new file mode 100644 index 0000000..91c3cc6 --- /dev/null +++ b/Load.HC @@ -0,0 +1,11 @@ +/* clang-format off */ + +#include "Patch"; +#include "Win"; +#include "KeyDev"; +#include "Status"; +#include "Saphir"; + +/* clang-format on */ + +WinTileHorz; \ No newline at end of file diff --git a/Patch.HC b/Patch.HC new file mode 100644 index 0000000..6f6549a --- /dev/null +++ b/Patch.HC @@ -0,0 +1,88 @@ +U0 @function_patch(U32 from, U32 to) { + *(from(U8 *)) = 0xE9; + *((from + 1)(I32 *)) = to - from - 5; +} + +U0 @gr_update_text_bg2() { + I64 reg RSI *dst = gr.dc2->body, reg R13 c, row, col, num_rows = TEXT_ROWS, + num_cols = TEXT_COLS, i, j, cur_ch, + reg R12 w1 = gr.dc2->width_internal, w2 = -7 * w1 + 8, + w3 = 7 * w1, w4 = 0; + U32 *src = gr.text_base; + Bool blink_flag = Blink; + U8 *dst2 = dst; + + if (gr.pan_text_x || gr.hide_col) { + gr.pan_text_x = ClampI64(gr.pan_text_x, -7, 7); + j = AbsI64(gr.pan_text_x) / FONT_WIDTH + 1; + num_cols -= j; + if (gr.pan_text_x < 0) { + src += j; + i = FONT_WIDTH * j + gr.pan_text_x; + } else + i = gr.pan_text_x; + dst2 = dst(U8 *) + i; + w4 = j; + w3 += j * FONT_WIDTH; + + j *= FONT_WIDTH; + dst(U8 *) = gr.dc2->body; + for (row = num_rows * FONT_HEIGHT; row--;) { + for (col = i; col--;) + *dst(U8 *)++ = 0; + dst(U8 *) += w1 - i - j; + for (col = j; col--;) + *dst(U8 *)++ = 0; + } + } + dst = dst2; + + if (gr.pan_text_y || gr.hide_row) { + gr.pan_text_y = ClampI64(gr.pan_text_y, -7, 7); + j = AbsI64(gr.pan_text_y) / FONT_HEIGHT + 1; + num_rows -= j; + if (gr.pan_text_y < 0) { + src += w1 / FONT_WIDTH * j; + i = w1 * (FONT_HEIGHT * j + gr.pan_text_y); + } else + i = w1 * gr.pan_text_y; + dst2 = dst(U8 *) + i; + + j *= w1 * FONT_HEIGHT; + dst(U8 *) = gr.dc2->body; + for (row = i; row--;) + *dst(U8 *)++ = 0; + dst(U8 *) = + gr.dc2->body + TEXT_ROWS * TEXT_COLS * FONT_HEIGHT * FONT_WIDTH - j; + for (row = j; row--;) + *dst(U8 *)++ = 0; + } + dst = dst2; + + for (row = num_rows; row--;) { + for (col = num_cols; col--;) { + cur_ch = *src++; + if (cur_ch & (ATTRF_SEL | ATTRF_INVERT | ATTRF_BLINK)) { + if (cur_ch & ATTRF_SEL) + cur_ch.u8[1] = cur_ch.u8[1] ^ 0xFF; + if (cur_ch & ATTRF_INVERT) + cur_ch.u8[1] = cur_ch.u8[1] << 4 + cur_ch.u8[1] >> 4; + if (cur_ch & ATTRF_BLINK && blink_flag) + cur_ch.u8[1] = 0x30; + else + cur_ch.u8[1] = 0xFF; + } + c = gr.to_8_colors[cur_ch.u8[1] >> 4]; + MOV U64[RSI], R13 ADD RSI, R12 MOV U64[RSI], R13 ADD RSI, + R12 MOV U64[RSI], R13 ADD RSI, R12 MOV U64[RSI], R13 ADD RSI, + R12 MOV U64[RSI], R13 ADD RSI, R12 MOV U64[RSI], R13 ADD RSI, + R12 MOV U64[RSI], R13 ADD RSI, R12 MOV U64[RSI], R13 dst(U8 *) += w2; + } + src += w4; + dst(U8 *) += w3; + } +} + +@function_patch(&GrUpdateTextBG, + &@gr_update_text_bg2); // Patch GrUpdateTextBG to make cursor + // blinking less irritating \ No newline at end of file diff --git a/README.md b/README.md index 8ff9bb9..540a6a6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ # saphir -Tiling window extensions for TempleOS WinMgr \ No newline at end of file +Tiling window extensions for TempleOS WinMgr + +![saphir](https://git.checksum.fail/alec/saphir/raw/branch/master/screenshot.png? "saphir") + +# Info + +Saphir extends the existing TempleOS WinMgr to provide tiling window functionality, as well as provide some sane defaults to those who prefer a less "blinky" interface. Saphir does not require you to recompile your Kernel; any changes to existing functions are live-patched and not persistent. + +# Usage + +`#include "Run";` + +# Keyboard shortcuts + +`ALT` + arrow keys : navigate windows +`ALT` + `h` : split window horizontal +`ALT` + `v` : split window vertical + diff --git a/Run.HC b/Run.HC new file mode 100644 index 0000000..6d2bfee --- /dev/null +++ b/Run.HC @@ -0,0 +1,3 @@ +Adam("StrPrint(Fs->cur_dir, \"/\");\n"); +Adam("Fs->cur_dv = Let2Drv('T');\n"); +AdamFile("Load"); \ No newline at end of file diff --git a/Saphir.HC b/Saphir.HC new file mode 100644 index 0000000..4059d07 --- /dev/null +++ b/Saphir.HC @@ -0,0 +1,19 @@ +U0 SaphirTask() { + I64 count; + I64 i; + CTask *task; + while (1) { + count = @windowed_task_count; + for (i = 0; i < count; i++) { + task = @windowed_task_index(i); + @set_border_doc_for_win(task); + @set_cursor_for_focused_win(task); + @draw_saphir_border_for_win(task); + @ensure_win_no_overlap_status_bar(task); + } + @update_status_bar; + Sleep(1); + } +} + +Spawn(&SaphirTask, , "Saphir"); \ No newline at end of file diff --git a/Status.HC b/Status.HC new file mode 100644 index 0000000..dc4142b --- /dev/null +++ b/Status.HC @@ -0,0 +1,7 @@ +U0 @update_status_bar() { + gr.dc->color = BLACK; + GrRect(gr.dc, 0, GR_HEIGHT - 8, GR_WIDTH, 8); + gr.dc->color = LTGRAY; + GrPrint(gr.dc, 0, GR_HEIGHT - 8, "[0x%08x] %s", sys_focus_task, + sys_focus_task->task_title); +} \ No newline at end of file diff --git a/Win.HC b/Win.HC new file mode 100644 index 0000000..4227787 --- /dev/null +++ b/Win.HC @@ -0,0 +1,175 @@ +#define SAPHIR_WIN_UP 0 +#define SAPHIR_WIN_DOWN 1 +#define SAPHIR_WIN_LEFT 2 +#define SAPHIR_WIN_RIGHT 3 + +CDoc *SAPHIR_BORDER_DOC = DocNew; + +CTask *@is_task_windowed(CTask *task) { + if ((task->display_flags & 1 << DISPLAYf_SHOW) && + ((task->display_flags & 1 << DISPLAYf_NOT_RAW))) + return task; + return NULL; +} + +I64 @windowed_task_count() { + CTask *task; + I64 count = 0; + task = adam_task->next_task; + while (task != adam_task) { + if (@is_task_windowed(task)) + count++; + task = task->next_task; + } + return count; +} + +CTask *@windowed_task_index(I64 index) { + CTask *task; + I64 count = 0; + task = adam_task->next_task; + while (task != adam_task) { + if (@is_task_windowed(task)) { + if (count == index) + return task; + count++; + } + task = task->next_task; + } + return NULL; +} + +U0 @set_border_doc_for_win(CTask *task) { + task->border_doc = SAPHIR_BORDER_DOC; +} + +U0 @draw_saphir_border_for_win(CTask *task) { + I64 color = LTGRAY; + if (task == sys_focus_task) + color = LTRED; + I64 x; + I64 y; + I64 wl = task->win_left - 1; + I64 wr = task->win_right + 1; + I64 wt = task->win_top - 1; + I64 wb = task->win_bottom + 1; + for (x = wl; x < wr + 1; x++) { + gr.text_base[(wt * TEXT_COLS) + x].u8[1] = color; + gr.text_base[(wb * TEXT_COLS) + x].u8[1] = color; + } + for (y = wt; y < wb + 1; y++) { + gr.text_base[(y * TEXT_COLS) + wl].u8[1] = color; + gr.text_base[(y * TEXT_COLS) + wr].u8[1] = color; + } +} + +U0 @ensure_win_no_overlap_status_bar(CTask *task) { + task->win_bottom = MinI64(task->win_bottom, TEXT_ROWS - 3); +} + +U0 @set_cursor_for_focused_win(CTask *task) { + if (task == sys_focus_task) { + task->put_doc->flags &= ~(1 << DOCf_HIDE_CURSOR); + } else { + task->put_doc->flags |= (1 << DOCf_HIDE_CURSOR); + } +} + +U0 @saphir_win_select(I64 dir) { + CTask *task = sys_focus_task; + I64 wt = task->win_top; + I64 wl = task->win_left; + I64 i; + I64 j; + + switch (dir) { + case SAPHIR_WIN_UP: + i = wt - 1; + j = -1; + break; + case SAPHIR_WIN_DOWN: + i = wt + 1; + j = 1; + break; + case SAPHIR_WIN_LEFT: + i = wl - 1; + j = -1; + break; + case SAPHIR_WIN_RIGHT: + i = wl + 1; + j = 1; + break; + } + + CTask *task1; + + switch (dir) { + case SAPHIR_WIN_UP: + case SAPHIR_WIN_DOWN: + for (i = i; i > 0 && i < TEXT_ROWS + 1; i += j) { + task1 = adam_task->next_task; + while (task1 != adam_task) { + if (@is_task_windowed(task1)) { + if (task1->win_top == i && task1->win_left == wl) { + WinFocus(task1); + return; + } + } + task1 = task1->next_task; + } + } + break; + case SAPHIR_WIN_LEFT: + case SAPHIR_WIN_RIGHT: + for (i = i; i > 0 && i < TEXT_COLS + 1; i += j) { + task1 = adam_task->next_task; + while (task1 != adam_task) { + if (@is_task_windowed(task1)) { + if (task1->win_left == i) { + WinFocus(task1); + return; + } + } + task1 = task1->next_task; + } + } + break; + default: + break; + } +} + +U0 @saphir_split_horz() { + CTask *task1 = sys_focus_task; + I64 wt = task1->win_top; + I64 wl = task1->win_left; + I64 wb = task1->win_bottom; + I64 wr = task1->win_right; + + task1->win_bottom = wb / 2; + + CTask *task2 = User; + task2->win_top = (wb / 2) + 2; + task2->win_bottom = wb; + task2->win_left = wl; + task2->win_right = wr; + + WinZBufUpdate; +} + +U0 @saphir_split_vert() { + CTask *task1 = sys_focus_task; + I64 wt = task1->win_top; + I64 wb = task1->win_bottom; + I64 wr = task1->win_right; + + task1->win_right = wr / 2; + + CTask *task2 = User; + task2->win_top = wt; + task2->win_bottom = wb; + task2->win_left = (wr / 2) + 2; + task2->win_right = wr; + + WinZBufUpdate; +} \ No newline at end of file diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..655a0941bc27ff3621a824b99ffbd69ebe6cfa1d GIT binary patch literal 13736 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 ZcXE~AV3r5w literal 0 HcmV?d00001