From 20678ed7f076b46c514a9c989d790bfde851d00d Mon Sep 17 00:00:00 2001
From: Frater <13979047+saintfrater@users.noreply.github.com>
Date: Fri, 27 Feb 2026 15:56:11 +0100
Subject: [PATCH 1/5] window manager start
---
src/gui/lib-api.asm | 5 +-
src/gui/lib-logic.asm | 3 +
src/gui/{window.asm => lib-wm-draw.asm} | 60 ------------
src/gui/lib-wm.asm | 121 ++++++++++++++++++++++++
4 files changed, 125 insertions(+), 64 deletions(-)
rename src/gui/{window.asm => lib-wm-draw.asm} (78%)
create mode 100644 src/gui/lib-wm.asm
diff --git a/src/gui/lib-api.asm b/src/gui/lib-api.asm
index d173e67..d57742f 100644
--- a/src/gui/lib-api.asm
+++ b/src/gui/lib-api.asm
@@ -86,11 +86,11 @@ gui_api_table:
; --- Structure d'un OBJET (Bouton, etc) ---
struc widget
- .header resw 1 ; HEADER
.state resb 1 ; État (0=libre, >0=utilisé)
.type resb 1 ; Type (0=Button, 1=Slider...)
.oldstate resb 1 ; widget a-t-il été modifié ?
.user_id resb 1 ; ID unique utilisateur
+ .win_id resb 1 ; ID de la fenêtre propriétaire
.x resw 1 ; Position Xevent_drag
.y resw 1 ; Position Y
.w resw 1 ; Largeur
@@ -254,9 +254,6 @@ gui_api_create:
mov cx, widget_size
div cx
- mov bx, 0xDEAD
- mov word [gs:si + widget.header], bx
-
mov bx, .type
mov byte [gs:si + widget.type], bl
mov bx, .x
diff --git a/src/gui/lib-logic.asm b/src/gui/lib-logic.asm
index 3fab1b0..433f797 100644
--- a/src/gui/lib-logic.asm
+++ b/src/gui/lib-logic.asm
@@ -26,6 +26,9 @@ gui_process_all:
mov bl, [PTR_MOUSE + mouse.status]
pop ds
+ ; Récupérer l'ID de la fenêtre active
+ mov dl, [wm_active_window_id]
+
mov si, 0 ; Pointeur début tableau
mov di, GUI_MAX_WIDGETS ; Compteur
diff --git a/src/gui/window.asm b/src/gui/lib-wm-draw.asm
similarity index 78%
rename from src/gui/window.asm
rename to src/gui/lib-wm-draw.asm
index da6d9bd..5d22443 100644
--- a/src/gui/window.asm
+++ b/src/gui/lib-wm-draw.asm
@@ -1,63 +1,3 @@
-; =============================================================================
-; Project : Custom BIOS / ROM
-; File : gui/window.asm
-; Author : frater
-;
-; License : GNU General Public License v3.0 or later (GPL-3.0+)
-;
-; 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 .
-;
-; =============================================================================
-
-%macro GUI 1-*
- ; %1 est l'index de la fonction
- ; On itère sur les arguments suivants en sens inverse (Convention C)
-
- %rep %0 - 1 ; Répéter pour (Nombre d'args - 1)
- %rotate -1 ; Prend le dernier argument
- push %1 ; L'empile
- %endrep
-
- %rotate -1 ; On revient au premier argument (l'index de fonction)
- call word [cs:graph_driver + ((%1)*2)]
-
- ; Nettoyage de la pile (Convention CDECL: l'appelant nettoie)
- ; Chaque argument = 2 octets (1 word).
- add sp, (%0 - 1) * 2
-%endmacro
-
-; ------------------------------------------------------------
-; TABLE DE SAUT (VECTEURS API)
-; Cette table doit être située au début du driver pour être
-; accessible par la GUI à des offsets fixes.
-; ------------------------------------------------------------
-%define WINDOW 0
-
-; ------------------------------------------------------------
-; COMMENTAIRE SUR LA MÉTHODE D'APPEL
-;
-; GFX FUNCTION, Arg1, Arg2, Arg3
-;
-; GFX GFX_PUTPIXEL, 320, 100, 1
-;
-; Si vous changez le code du driver, tant que la table au début
-; ne change pas d'ordre, la GUI n'a pas besoin d'être recompilée.
-; ------------------------------------------------------------
-
-gui_driver:
- dw draw_window
-
; =============================================================================
; Fonction : draw_window
; Description : Dessine une fenêtre style Mac System 1.0
diff --git a/src/gui/lib-wm.asm b/src/gui/lib-wm.asm
new file mode 100644
index 0000000..b5ad4ee
--- /dev/null
+++ b/src/gui/lib-wm.asm
@@ -0,0 +1,121 @@
+; =============================================================================
+; Project : Custom BIOS / ROM
+; File : gui/window.asm
+; Author : frater
+;
+; License : GNU General Public License v3.0 or later (GPL-3.0+)
+;
+; 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 .
+;
+; =============================================================================
+
+; =============================================================================
+; Project : Custom BIOS / ROM
+; File : gui/window-manager.asm
+; Description : Gestionnaire de fenêtres (Window Manager)
+; =============================================================================
+
+%define MAX_WINDOWS 8
+
+; --- États d'une fenêtre ---
+%define WIN_STATE_FREE 0 ; Slot libre en mémoire
+%define WIN_STATE_INACTIVE 1 ; Affichée en arrière-plan
+%define WIN_STATE_ACTIVE 2 ; Au premier plan (focus)
+%define WIN_STATE_HIDDEN 3 ; Invisible
+
+struc window
+ .state resb 1 ; État de la fenêtre
+ .id resb 1 ; Identifiant unique
+ .x resw 1
+ .y resw 1
+ .w resw 1
+ .h resw 1
+ .title_ofs resw 1 ; Pointeur texte du titre
+ .title_seg resw 1
+ .process_cb resw 1 ; NOUVEAU : Offset du callback (processus de la fenêtre)
+ .process_seg resw 1 ; Segment du callback
+endstruc
+
+%define window_size 16
+
+; Variables globales du WM (à placer dans le segment BDA_CUSTOM ou SEG_GUI)
+wm_active_window_id db 0 ; ID de la fenêtre actuellement active
+
+; -----------------------------------------------------------------------------
+; wm_process_all
+; Remplace la boucle principale. Gère les processus de fenêtre puis la GUI.
+; -----------------------------------------------------------------------------
+wm_process_all:
+ pusha
+ push ds
+ push gs
+
+ mov ax, SEG_GUI
+ mov gs, ax
+
+ ; 1. Gérer les processus des fenêtres
+ mov si, 0 ; On suppose que le pool window commence à l'offset 0 d'une zone dédiée
+ mov cx, MAX_WINDOWS
+
+ .loop_windows:
+ cmp byte [gs:si + window.state], WIN_STATE_FREE
+ je .next_window
+
+ ; VÉRIFICATION : Est-ce que la fenêtre est ACTIVE ?
+ ; Selon votre règle : on n'appelle pas le processus si elle n'est pas active.
+ cmp byte [gs:si + window.state], WIN_STATE_ACTIVE
+ jne .next_window
+
+ ; La fenêtre est active, a-t-elle un processus (callback) défini ?
+ cmp word [gs:si + window.process_cb], 0
+ je .next_window
+
+ ; Exécution du processus spécifique à la fenêtre active
+ pusha
+ ; Appel inter-segment (Far Call) ou intra-segment (Near Call) selon l'architecture
+ ; Ici on simule un near call si tout est dans CS
+ call word [gs:si + window.process_cb]
+ popa
+
+ .next_window:
+ add si, window_size
+ loop .loop_windows
+
+ ; 2. Appeler le moteur d'événements des widgets (lib-logic)
+ call gui_process_all
+
+ pop gs
+ pop ds
+ popa
+ ret
+
+; -----------------------------------------------------------------------------
+; wm_set_active
+; Change la fenêtre active (Focus)
+; In: AL = window_id
+; -----------------------------------------------------------------------------
+wm_set_active:
+ push gs
+ push bx
+
+ ; Sauvegarder le nouvel ID actif
+ mov [wm_active_window_id], al
+
+ ; Note : Ici vous pourriez rajouter une boucle pour passer toutes les
+ ; autres fenêtres en WIN_STATE_INACTIVE et redessiner le tout pour
+ ; mettre la fenêtre active au premier plan (Z-Order).
+
+ pop bx
+ pop gs
+ ret
\ No newline at end of file
From c2d51bfc16e2fe3659a319339ed008c6d5143e7c Mon Sep 17 00:00:00 2001
From: Frater <13979047+saintfrater@users.noreply.github.com>
Date: Fri, 27 Feb 2026 15:58:53 +0100
Subject: [PATCH 2/5] merge main
---
.gitignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 7a5bd8d..4a395c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@ build/
gfx/
roms/
tools/
-doc/generated/
\ No newline at end of file
+doc/generated/
+fonts/
From a1366a2fc9967c7a4eb3f3d8f6412bc8ae2c30b0 Mon Sep 17 00:00:00 2001
From: Frater <13979047+saintfrater@users.noreply.github.com>
Date: Fri, 27 Feb 2026 16:29:10 +0100
Subject: [PATCH 3/5] buiding GUI BDA
---
src/common/bda.asm | 5 +++++
src/gui/lib-logic.asm | 2 +-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/common/bda.asm b/src/common/bda.asm
index 68aa575..d9d9b7b 100644
--- a/src/common/bda.asm
+++ b/src/common/bda.asm
@@ -30,7 +30,9 @@
%define PTR_MOUSE 0x0000
%define PTR_GFX (PTR_MOUSE + mouse_size)
+%define PTR_GUI (PTR_GFX + gfx_size)
+;
; video related information (compatible with VGA bios)
%define BDA_VIDEO_CURR_MODE 0x0049
%define BDA_VIDEO_COLUMNS 0x004A
@@ -76,7 +78,10 @@ struc gfx
.cur_offset resw 1 ; offset calculé pour le prochain caractère
.cur_line_ofs resw 1 ; "interligne" +2000h ou -2000h
.cur_shift resb 1 ; x&7 (0..7)
+endstruc
+struc gui
+ .wm_active_window_id resb 1 ; ID de la fenêtre active
endstruc
; -----------------------------------------------------------------------------------
diff --git a/src/gui/lib-logic.asm b/src/gui/lib-logic.asm
index e7494d6..b3ef991 100644
--- a/src/gui/lib-logic.asm
+++ b/src/gui/lib-logic.asm
@@ -27,7 +27,7 @@ gui_process_all:
pop ds
; Récupérer l'ID de la fenêtre active
- mov dl, [wm_active_window_id]
+ mov dl, [PTR_GUI + gui.wm_active_window_id]
mov si, 0 ; Pointeur début tableau
mov di, GUI_MAX_WIDGETS ; Compteur
From 14a5ec116a3c5d1047d3d0c581ee3c1cac887177 Mon Sep 17 00:00:00 2001
From: Frater <13979047+saintfrater@users.noreply.github.com>
Date: Fri, 27 Feb 2026 22:23:18 +0100
Subject: [PATCH 4/5] fixing push/pop order in api_set_text & api_set_mode
---
src/boot.asm | 23 +++++++++++++++--------
src/drivers/mouse_ps2.asm | 3 ++-
src/gui/lib-api.asm | 37 +++++++++++++++++++++++++++++--------
src/gui/lib-logic.asm | 11 +++++++++++
4 files changed, 57 insertions(+), 17 deletions(-)
diff --git a/src/boot.asm b/src/boot.asm
index 5b6ef65..43958f0 100644
--- a/src/boot.asm
+++ b/src/boot.asm
@@ -204,12 +204,19 @@ main_loop:
mov .value, 0xFADE
.loop:
- call gui_process_all
- GUI OBJ_GET_VAL, .my_slider
- mov .value, ax
- cmp ax,.oldval
- je .loop
+ ISADBG ISA_RIGHT, 0x11
+
+ call gui_process_all
+
+ ; --- TRACE 12 : GUI traitée ---
+ ISADBG ISA_RIGHT, 0x12
+
+ ; GUI OBJ_GET_VAL, .my_slider
+ ;mov .value, ax
+
+ ;cmp ax,.oldval
+ ;je .loop
; debug
;mov .oldval, ax
@@ -228,9 +235,9 @@ main_loop:
;mov al, 0x00
;int 0x10
- mov dx, CRTC_COLOR_DATA
- and ax, 0x001F
- out dx, ax
+ ;mov dx, CRTC_COLOR_DATA
+ ;and ax, 0x001F
+ ;out dx, ax
jmp .loop
leave
diff --git a/src/drivers/mouse_ps2.asm b/src/drivers/mouse_ps2.asm
index 9b3c9ab..af6bd98 100644
--- a/src/drivers/mouse_ps2.asm
+++ b/src/drivers/mouse_ps2.asm
@@ -291,6 +291,7 @@ isr_mouse_handler:
in al, i8042_PS2_DATA ; lire octet souris
movzx bx, byte [PTR_MOUSE + mouse.idx]
+
cmp bl,0
jne .read_packet
@@ -299,6 +300,7 @@ isr_mouse_handler:
jz .done_eoi ; allignement erroné
.read_packet:
+
mov byte [PTR_MOUSE + mouse.buffer + bx], al
inc byte [PTR_MOUSE + mouse.idx]
@@ -372,7 +374,6 @@ isr_mouse_handler:
.done:
; update la position du curseur sur l'écran
; en ce moment c'est commented out (debug)
- ; call cga_mouse_cursor_move
GFX MOUSE_MOVE
.done_eoi:
mov al, 0x20
diff --git a/src/gui/lib-api.asm b/src/gui/lib-api.asm
index d57742f..08d95a8 100644
--- a/src/gui/lib-api.asm
+++ b/src/gui/lib-api.asm
@@ -133,17 +133,18 @@ endstruc
; -----------------------------------------------------------------------------
; gui_api_slider_attr
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
%define .min word [bp+6]
%define .max word [bp+8]
%define .val word [bp+10]
%define .pct word [bp+12]
+; -----------------------------------------------------------------------------
gui_api_slider_attr:
push bp
mov bp, sp
push gs
push ax
+ push si
mov ax, .id
call gui_get_widget_ptr_internal
@@ -161,23 +162,29 @@ gui_api_slider_attr:
call gui_slider_update_pixels
.err:
+ pop si
pop ax
pop gs
leave
+ %undef .id
+ %undef .min
+ %undef .max
+ %undef .val
+ %undef .pct
ret
-%undef .id
-%undef .mode
+
; -----------------------------------------------------------------------------
; gui_api_set_mode
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
%define .mode word [bp+6]
+; -----------------------------------------------------------------------------
gui_api_set_mode:
push bp
mov bp, sp
push gs
push ax
+ push si
mov ax, .id
call gui_get_widget_ptr_internal
@@ -187,8 +194,9 @@ gui_api_set_mode:
mov byte [gs:si + widget.attr_mode], al
.err:
- pop gs
+ pop si
pop ax
+ pop gs
leave
ret
%undef .id
@@ -205,6 +213,7 @@ gui_api_set_text:
mov bp, sp
push gs
push ax
+ push si
mov ax, .id
call gui_get_widget_ptr_internal
@@ -216,8 +225,9 @@ gui_api_set_text:
mov word [gs:si + widget.text_ofs], ax
.err:
- pop gs
+ pop si
pop ax
+ pop gs
leave
ret
%undef .id
@@ -288,6 +298,7 @@ gui_api_get_state:
push bp
mov bp, sp
push gs
+ push si
mov ax, .id
call gui_get_widget_ptr_internal
@@ -300,6 +311,7 @@ gui_api_get_state:
.err:
mov ax, -1
.done:
+ pop si
pop gs
leave
ret
@@ -315,6 +327,7 @@ gui_api_get_type:
push bp
mov bp, sp
push gs
+ push si
mov ax,.id
call gui_get_widget_ptr_internal
@@ -327,6 +340,7 @@ gui_api_get_type:
.err:
mov ax, -1
.done:
+ pop si
pop gs
leave
ret
@@ -342,6 +356,8 @@ gui_api_get_val:
push bp
mov bp, sp
push gs
+ push si
+
mov ax,.id
call gui_get_widget_ptr_internal
@@ -354,6 +370,7 @@ gui_api_get_val:
.err:
mov ax, -1
.done:
+ pop si
pop gs
leave
ret
@@ -370,6 +387,7 @@ gui_api_set_val:
push bp
mov bp, sp
push gs
+ push si
mov ax,.id
call gui_get_widget_ptr_internal
@@ -379,6 +397,7 @@ gui_api_set_val:
mov [gs:si + widget.thumb_pos], ax
.err:
+ pop si
pop gs
leave
ret
@@ -396,6 +415,7 @@ gui_api_destroy:
push bp
mov bp, sp
push gs
+ push si
mov ax,.id
call gui_get_widget_ptr_internal
@@ -408,6 +428,7 @@ gui_api_destroy:
.err:
mov ax, -1
.done:
+ pop si
pop gs
leave
ret
@@ -469,7 +490,7 @@ gui_get_widget_ptr_internal:
; Entrée : DS doit pointer vers SEG_GUI
; -----------------------------------------------------------------------------
gui_init_system:
- push eax
+ push ax
push cx
push es
push di
@@ -486,7 +507,7 @@ gui_init_system:
pop di
pop es
pop cx
- pop eax
+ pop ax
ret
; -----------------------------------------------------------------------------
diff --git a/src/gui/lib-logic.asm b/src/gui/lib-logic.asm
index b3ef991..b33d88b 100644
--- a/src/gui/lib-logic.asm
+++ b/src/gui/lib-logic.asm
@@ -37,9 +37,15 @@ gui_process_all:
cmp byte [gs:si + widget.state], GUI_STATE_FREE
je .next_widget
+ mov ax, di
+ ISADBG ISA_LEFT, al
+
; --- Logique Widget ---
push bx ; Sauver état boutons souris
+
+ ISADBG ISA_RIGHT, 0x20 ; Avant logic
call gui_update_logic ; Vérifier collision/clic
+ ISADBG ISA_RIGHT, 0x21 ; Avant logic
; Si AL=1 (Clic relâché validé), exécuter le callback
cmp al, 1
@@ -63,10 +69,15 @@ gui_process_all:
; Dessiner (si l'état a changé ou pour refresh)
+ ISADBG ISA_RIGHT, 0x22 ; Avant dessin
+
cmp al,ah
je .next_widget ; Si != 0, le widget est à jour, on passe.
call gui_draw_single_widget
+ ISADBG ISA_RIGHT, 0x23 ; Avant dessin
+
+
.next_widget:
add si, widget_size
dec di
From b2634ce58a3e344356b5a9647af446f25f0f61cc Mon Sep 17 00:00:00 2001
From: Frater <13979047+saintfrater@users.noreply.github.com>
Date: Fri, 27 Feb 2026 22:41:31 +0100
Subject: [PATCH 5/5] try to fix "hello" & hslider in 86Box
---
src/drivers/mouse_ps2.asm | 2 +-
src/gui/lib-api.asm | 37 ++++++++++++++++++++-----------------
src/gui/lib-draw.asm | 2 +-
3 files changed, 22 insertions(+), 19 deletions(-)
diff --git a/src/drivers/mouse_ps2.asm b/src/drivers/mouse_ps2.asm
index af6bd98..ab40dd9 100644
--- a/src/drivers/mouse_ps2.asm
+++ b/src/drivers/mouse_ps2.asm
@@ -373,8 +373,8 @@ isr_mouse_handler:
.done:
; update la position du curseur sur l'écran
- ; en ce moment c'est commented out (debug)
GFX MOUSE_MOVE
+
.done_eoi:
mov al, 0x20
out i8259_SLAVE_CMD, al ; EOI PIC esclave
diff --git a/src/gui/lib-api.asm b/src/gui/lib-api.asm
index 08d95a8..dd33295 100644
--- a/src/gui/lib-api.asm
+++ b/src/gui/lib-api.asm
@@ -198,16 +198,16 @@ gui_api_set_mode:
pop ax
pop gs
leave
+ %undef .id
+ %undef .mode
ret
-%undef .id
-%undef .mode
; -----------------------------------------------------------------------------
; gui_api_set_text
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
%define .segmt word [bp+6]
%define .oft word [bp+8]
+; -----------------------------------------------------------------------------
gui_api_set_text:
push bp
mov bp, sp
@@ -229,10 +229,11 @@ gui_api_set_text:
pop ax
pop gs
leave
+ %undef .id
+ %undef .text_ofs
+ %undef .text_seg
ret
-%undef .id
-%undef .text_ofs
-%undef .text_seg
+
; -----------------------------------------------------------------------------
; gui_api_create
@@ -292,8 +293,8 @@ gui_api_create:
; gui_api_get_state
; Arg1: ID
; Out: AX = State
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
+; -----------------------------------------------------------------------------
gui_api_get_state:
push bp
mov bp, sp
@@ -314,15 +315,15 @@ gui_api_get_state:
pop si
pop gs
leave
+ %undef .id
ret
-%undef .id
; -----------------------------------------------------------------------------
; gui_api_get_type
; Arg1: ID
; Out: AX = Type
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
+; -----------------------------------------------------------------------------
gui_api_get_type:
push bp
mov bp, sp
@@ -350,8 +351,8 @@ gui_api_get_type:
; gui_api_get_val
; Arg1: ID
; Out: AX = Attr Val
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
+; -----------------------------------------------------------------------------
gui_api_get_val:
push bp
mov bp, sp
@@ -373,16 +374,17 @@ gui_api_get_val:
pop si
pop gs
leave
+ %undef .id
ret
-%undef .id
+
; -----------------------------------------------------------------------------
; gui_api_set_val
; Arg1: ID
; Arg2: Value
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
%define .val word [bp+6]
+; -----------------------------------------------------------------------------
gui_api_set_val:
push bp
mov bp, sp
@@ -400,17 +402,17 @@ gui_api_set_val:
pop si
pop gs
leave
+ %undef .id
+ %undef .val
ret
-%undef .id
-%undef .val
; -----------------------------------------------------------------------------
; gui_api_destroy
; Détruit un widget via son ID
; Arg1: ID
; Out: AX = 0 (OK), -1 (Error)
-; -----------------------------------------------------------------------------
%define .id word [bp+4]
+; -----------------------------------------------------------------------------
gui_api_destroy:
push bp
mov bp, sp
@@ -431,8 +433,9 @@ gui_api_destroy:
pop si
pop gs
leave
+ %undef .id
ret
-%undef .id
+
; -----------------------------------------------------------------------------
; gui_get_widget_ptr
@@ -448,8 +451,8 @@ gui_get_widget_ptr:
call gui_get_widget_ptr_internal
pop ax
leave
+ %undef .id
ret
-%undef .id
; -----------------------------------------------------------------------------
; gui_get_widget_ptr_internal
diff --git a/src/gui/lib-draw.asm b/src/gui/lib-draw.asm
index d435cb5..9b9945b 100644
--- a/src/gui/lib-draw.asm
+++ b/src/gui/lib-draw.asm
@@ -522,7 +522,7 @@ draw_text:
push ax ; Sauve X original
mov ax, [gs:si + widget.w]
sub ax, cx
- shr ax, 1
+ sar ax, 1
pop dx ; Récupère X original (poussé depuis AX)
add ax, dx ; AX = X final centré